/* radare - LGPL - Copyright 2011-2016 - pancake */

#include <r_types.h>
#include <r_util.h>
#include <r_lib.h>
#include <r_bin.h>
#include "dex/dex.h"
#define r_hash_adler32 __adler32
#include "../../hash/adler32.c"

#define DEBUG_PRINTF 0

#if DEBUG_PRINTF
#define dprintf eprintf
#else
#define dprintf if (0)eprintf
#endif

static Sdb *mdb = NULL;

static int check(RBinFile *arch);
static int check_bytes(const ut8 *buf, ut64 length);

static Sdb* get_sdb (RBinObject *o) {
	if (!o || !o->bin_obj) return NULL;
	struct r_bin_dex_obj_t *bin = (struct r_bin_dex_obj_t *) o->bin_obj;
	if (bin->kv) return bin->kv;
	return NULL;
}

static void * load_bytes(RBinFile *arch, const ut8 *buf, ut64 sz, ut64 loadaddr, Sdb *sdb){
	void *res = NULL;
	RBuffer *tbuf = NULL;
	if (!buf || sz == 0 || sz == UT64_MAX) return NULL;
	tbuf = r_buf_new ();
	r_buf_set_bytes (tbuf, buf, sz);
	res = r_bin_dex_new_buf (tbuf);
	r_buf_free (tbuf);
	return res;
}

static int load(RBinFile *arch) {
	const ut8 *bytes = arch ? r_buf_buffer (arch->buf) : NULL;
	ut64 sz = arch ? r_buf_size (arch->buf): 0;

	if (!arch || !arch->o) return false;
	arch->o->bin_obj = load_bytes (arch, bytes, sz, arch->o->loadaddr, arch->sdb);
	return arch->o->bin_obj ? true: false;
}

static ut64 baddr(RBinFile *arch) {
	return 0;
}

static char *flagname (const char *class, const char *method) {
	if (method) {
		if (class) {
			return r_str_newf ("%s.%s", class, method);
		}
		return r_str_newf ("static.method.%s", method);
	}
	return NULL;
}

static int check(RBinFile *arch) {
	const ut8 *bytes = arch ? r_buf_buffer (arch->buf) : NULL;
	ut64 sz = arch ? r_buf_size (arch->buf): 0;
	return check_bytes (bytes, sz);
}

static int check_bytes(const ut8 *buf, ut64 length) {
	if (!buf || length < 8)
		return false;
	// Non-extended opcode dex file
	if (!memcmp (buf, "dex\n035\0", 8)) {
	        return true;
	}
	// Extended (jumnbo) opcode dex file, ICS+ only (sdk level 14+)
	if (!memcmp (buf, "dex\n036\0", 8))
	        return true;
	// M3 (Nov-Dec 07)
	if (!memcmp (buf, "dex\n009\0", 8))
	        return true;
	// M5 (Feb-Mar 08)
        if (!memcmp (buf, "dex\n009\0", 8))
	        return true;
	// Default fall through, should still be a dex file
	if (!memcmp (buf, "dex\n", 4))
                return true;
	return false;
}

static RBinInfo *info(RBinFile *arch) {
	RBinHash *h;
	RBinInfo *ret = R_NEW0 (RBinInfo);
	if (!ret) return NULL;
	ret->file = arch->file? strdup (arch->file): NULL;
	ret->type = strdup ("DEX CLASS");
	ret->has_va = false;
	ret->bclass = r_bin_dex_get_version (arch->o->bin_obj);
	ret->rclass = strdup ("class");
	ret->os = strdup ("linux");
	ret->subsystem = strdup ("any");
	ret->machine = strdup ("Dalvik VM");

	h = &ret->sum[0];
	h->type = "sha1";
	h->len = 20;
	h->addr = 12;
	h->from = 12;
	h->to = arch->buf->length-32;
	memcpy (h->buf, arch->buf->buf+12, 20);

	h = &ret->sum[1];
	h->type = "adler32";
	h->len = 4;
	h->addr = 0x8;
	h->from = 12;
	h->to = arch->buf->length-h->from;

	h = &ret->sum[2];
	h->type = 0;

	memcpy (h->buf, arch->buf->buf + 8, 4);
	{
		ut32 *fc = (ut32 *)(arch->buf->buf + 8);
		ut32  cc = __adler32 (arch->buf->buf + h->from, h->to);
		//ut8 *fb = (ut8*)fc, *cb = (ut8*)&cc;
		if (*fc != cc) {
			dprintf ("# adler32 checksum doesn't match. Type this to fix it:\n");
			dprintf ("wx `#sha1 $s-32 @32` @12 ; wx `#adler32 $s-12 @12` @8\n");
		}
	}

	ret->arch = strdup ("dalvik");
	ret->lang = "java";
	ret->bits = 32;
	ret->big_endian = 0;
	ret->dbg_info = 0; //1 | 4 | 8; /* Stripped | LineNums | Syms */
	return ret;
}

static RList* strings (RBinFile *arch) {
	struct r_bin_dex_obj_t *bin = NULL;
	RBinString *ptr = NULL;
	RList *ret = NULL;
	int i, len;
	ut8 buf[6];
	ut64 off;

	if (!arch || !arch->o)
		return NULL;
	bin = (struct r_bin_dex_obj_t *) arch->o->bin_obj;
	if (!bin || !bin->strings)
		return NULL;
	if (bin->header.strings_size > bin->size) {
		bin->strings = NULL;
		return NULL;
	}
	if (!(ret = r_list_new ()))
		return NULL;
	ret->free = free;
	for (i = 0; i < bin->header.strings_size; i++) {
		if (!(ptr = R_NEW0 (RBinString)))
			break;
		if (bin->strings[i] > bin->size || bin->strings[i] + 6 > bin->size)
			goto out_error;
		r_buf_read_at (bin->b, bin->strings[i], (ut8*)&buf, 6);
		len = dex_read_uleb128 (buf);
		if (len > 1 && len < R_BIN_SIZEOF_STRINGS) {
			ptr->string = malloc (len + 1);
			if (!ptr->string)
				goto out_error;
			off = bin->strings[i] + dex_uleb128_len (buf);
			if (off > bin->size || off + len > bin->size) {
				free (ptr->string);
				goto out_error;
			}
			r_buf_read_at (bin->b, off, (ut8*)ptr->string, len);
			ptr->string[len] = 0;
			ptr->vaddr = ptr->paddr = bin->strings[i];
			ptr->size = len;
			ptr->length = len;
			ptr->ordinal = i+1;
			r_list_append (ret, ptr);
		} else {
			dprintf ("dex_read_uleb128: invalid read\n");
			free (ptr);
		}
	}
	return ret;
out_error:
	r_list_free (ret);
	free (ptr);
	return NULL;
}

#if 0
static inline ut32 getmethodoffset (struct r_bin_dex_obj_t *bin, int n, ut32 *size) {
	ut8 *buf, *map_end, *map;
	ut32 mapsz, off = 0L;
	int left;
	*size = 0;
	map = buf = r_buf_get_at (bin->b, bin->header.data_offset, &left);
	if (!map) return 0;
	for (map_end = map+bin->header.data_size; map<map_end;) {
		int num = map[0] + (map[1]<<8);
		int ninsn = map[12] + (map[13]<<8);
		map += 16; // skip header
		mapsz = ninsn%2? (ninsn+1)*2: ninsn*2;
		if (n == num) {
			*size = mapsz;
			off = bin->header.data_offset + (size_t)(map - buf);
			break;
		}
		map += mapsz;
	}
	return off;
}
#endif
static char *getstr (RBinDexObj *bin, int idx) {
	const ut8 buf[8], *buf2;
	ut64 len;
	int uleblen;
	if (idx<0 || idx >= bin->header.strings_size || !bin->strings)
		return NULL;
	r_buf_read_at (bin->b, bin->strings[idx], (ut8*)&buf, sizeof (buf));
	len = dex_read_uleb128 (buf);
	if (len < 1)
		return NULL;
	buf2 = r_uleb128 (buf, sizeof (buf), &len);
	uleblen = (size_t)(buf2 - buf);
	if (len>0 && len < R_BIN_SIZEOF_STRINGS) {
		char *str = calloc (1, len+1);
		if (!str) return NULL;
		r_buf_read_at (bin->b, (bin->strings[idx])+uleblen,
				(ut8*)str, len); //uleblen);
		str[len] = 0;
		return str;
	}
	return NULL;
}

static char *get_string(RBinDexObj *bin, int cid, int idx) {
	char *c_name, *m_name, *res;
	if (idx < 0 || idx >= bin->header.strings_size) {
		return NULL;
	}
	if (cid < 0 || cid >= bin->header.strings_size) {
		return NULL;
	}
	c_name = getstr (bin, cid);
	m_name = getstr (bin, idx);
	if (c_name && *c_name == ',') {
		res = r_str_newf ("%s", m_name);
	} else {
		if (c_name && m_name) {
			res = r_str_newf ("method.%s", m_name);
		} else {
			if (c_name && m_name) {
				res = r_str_newf ("unk.%s", c_name);
			} else {
				res = r_str_newf ("UNKNOWN");
			}
		}
	}
	free (c_name);
	free (m_name);
	return res;
}

/* TODO: check boundaries */
static char *dex_method_name (RBinDexObj *bin, int idx) {
	if (idx < 0 || idx >= bin->header.method_size) {
		return NULL;
	}
	int cid = bin->methods[idx].class_id;
	int tid = bin->methods[idx].name_id;
	if (cid < 0 || cid >= bin->header.strings_size) {
		return NULL;
	}
	if (tid < 0 || tid >= bin->header.strings_size) {
		return NULL;
	}
	return get_string (bin, cid, tid);
}

static char *dex_class_name_byid (RBinDexObj *bin, int cid) {
	int tid;
	if (!bin || !bin->types) {
		return NULL;
	}
	//cid = c->super_class;
	if (cid < 0 || cid >= bin->header.types_size) {
		return NULL;
	}
	tid = bin->types [cid].descriptor_id;
	//int sid = bin->strings[tid];
	return get_string (bin, cid, tid);
}

static char *getClassName(const char *name) {
	const char *p;
	if (!name) {
		return NULL;
	}
	p = strstr (name, ".L");
	if (p) {
		char *q, *r = strdup (p + 2);
		q = strchr (r, ';');
		if (q) *q = 0;
		return r;
	}
	return NULL;
}

static char *dexFieldName(RBinDexObj *dex, RBinDexClass *c, int fn) {
	DexField field = { 0 };
	ut32 off = dex->header.fields_offset;
	int delta = fn * sizeof (DexField);
	if (delta > dex->header.fields_size) {
		return NULL;
	}
	r_buf_read_at (dex->b, off, (ut8*)&field + delta, sizeof (DexField));
	eprintf ("NAME = %d\n", field.name_id);
	return getstr (dex, field.name_id);
}

static char *dexFieldType(RBinDexObj *dex, RBinDexClass *c, int fn) {
	DexField field = { 0 };
	ut32 off = dex->header.fields_offset;
	int delta = fn * sizeof (DexField);
	if (delta > dex->header.fields_size) {
		return NULL;
	}
	r_buf_read_at (dex->b, off, (ut8*)&field + delta, sizeof (DexField));
	eprintf ("TYPE = %d\n", field.type_id);
	return getstr (dex, field.type_id);
}

static char *dex_class_name (RBinDexObj *bin, RBinDexClass *c) {
	int cid, tid;
	if (!bin || !c || !bin->types) {
		return NULL;
	}
	cid = c->class_id;
	//cid = c->super_class;
	if (cid < 0 || cid >= bin->header.types_size) {
		return NULL;
	}
	tid = bin->types [cid].descriptor_id;
	//int sid = bin->strings[tid];
	return get_string (bin, cid, tid);
}

static char *dex_type_name (RBinDexObj *bin, int id) {
	int cid, tid;
	if (!bin || !bin->types) {
		return NULL;
	}
	cid = id;
	if (cid < 0 || cid >= bin->header.types_size) {
		return NULL;
	}
	tid = bin->types [cid].descriptor_id;
	//int sid = bin->strings[tid];
	return get_string (bin, cid, tid);// cid, tid);
}

static char *dex_class_super_name (RBinDexObj *bin, RBinDexClass *c) {
	int cid, tid;
	if (!bin || !c || !bin->types) {
		return NULL;
	}
	cid = c->super_class;
	if (cid<0 || cid >= bin->header.types_size) {
		return NULL;
	}
	tid = bin->types [cid].descriptor_id;
	//int sid = bin->strings[tid];
	return get_string (bin, cid, tid);
}

static int *parse_class(RBinFile *binfile, struct r_bin_dex_obj_t *bin, struct dex_class_t *c, RBinClass *cls) {
	int i, *methods;
	ut64 SF, IF, DM, VM;
	const ut8 *p, *p_end;
	char *class_name;

	if (!c || !c->class_data_offset) {
		// no method here, just class definition
		//free (class_name);
		//free (super_name);
		return NULL;
	}
	// TODO: do not call twice
	class_name = dex_class_name (bin, c);
	if (!class_name) {
		return NULL;
	}
	methods = calloc (sizeof (int), bin->header.method_size);
	if (!methods) {
		free (class_name);
		return false;
	}
	dprintf ("  class_data_offset: %d\n", c->class_data_offset);
	p = r_buf_get_at (binfile->buf, c->class_data_offset, NULL);
	p_end = p + (binfile->buf->length - c->class_data_offset);
	/* data header */
	/* walk over class data items */
	p = r_uleb128 (p, p_end - p, &SF);
	p = r_uleb128 (p, p_end - p, &IF);
	p = r_uleb128 (p, p_end - p, &DM);
	p = r_uleb128 (p, p_end - p, &VM);
//eprintf ("METHODS %s %d\n", class_name, DM);
//eprintf ("SF %d IF %d DM %d VM %d\n", SF, IF, DM, VM);
	dprintf ("  static fields: %u\n", (ut32)SF);
	/* static fields */
	int lastidx = 0;
	//XXX r_buf_get_at broken with R_IO_MAX_ALLOC
	DexField *fields = (DexField *)r_buf_get_at (binfile->buf, bin->header.fields_offset, NULL);
	const ut8 *op = p;
	for (i = 0; i < SF; i++) {
#if 0
		ut64 FI, FA, FN;
#if 0
		p = r_uleb128 (p, p_end - p, &FI);
		p = r_uleb128 (p, p_end - p, &FA);
		dprintf ("    field_idx: %u\n", (ut32)FI);
		char *field_name = get_string (bin, c, (ut32)FA); //bin->methods[i].class_id);
#endif
		//p = r_uleb128 (p, p_end - p, &FN); // type
		p = r_uleb128 (p, p_end - p, &FI); // name
		p = r_uleb128 (p, p_end - p, &FA); // access
		//p = r_uleb128 (p, p_end-p, &FN); // field name
		//char *name = dex_type_name (bin, FN)/;
		//eprintf ("SFIELD %s %s %d acc %x\n", class_name, name, FN, FA);
//const char *foo = dex_type_name (bin, FI);
//eprintf ("---- %s\n", foo);
	//	eprintf ("SFIELD %s %d\n", class_name, FI);
// TODO: retrieve name of field here
// TODO: add comment or store that fcn var info in sdb directly
		eprintf ("    field access_flags: 0x%x\n", (ut32)FA);
#endif
		ut64 fieldIdx, accessFlags;
		int fieldOffset = bin->header.fields_offset + (p - op);
		char *name = NULL;
		p = r_uleb128 (p, p_end - p, &fieldIdx); // fieldIdx
		p = r_uleb128 (p, p_end - p, &accessFlags); // accessFlags
		fieldIdx += lastidx;
		if (bin->header.fields_offset + sizeof (DexField) * fieldIdx > bin->size) {
			free (methods);
			return NULL;
		}
		name = getstr (bin, fields[fieldIdx].name_id); //fields[lastidx].name_id);
		class_name = r_str_replace (class_name, "method.", "", 0);
		class_name = r_str_replace (class_name, ";", "_", 0);
		//eprintf ("f sym.%s.static.field.%d_%s = %d\n", class_name, i, name, fieldOffset);
		{
			RBinSymbol *sym = R_NEW0 (RBinSymbol);
			sym->name = r_str_newf ("%s.static.%d_%s", class_name, i, name);
			sym->type = r_str_const ("STATIC");
			sym->paddr = sym->vaddr = fieldOffset;
			r_list_append (bin->methods_list, sym);
		}
		lastidx = fieldIdx;
	}
	/* instance fields */
	//eprintf ("  instance fields: %u\n", (ut32)IF);
	lastidx = 0;
	op = p;
	for (i = 0; i < IF; i++) {
		ut64 fieldIdx, accessFlags;
		int fieldOffset = bin->header.fields_offset + (p - op);
		p = r_uleb128 (p, p_end - p, &fieldIdx); // fieldIdx
		p = r_uleb128 (p, p_end - p, &accessFlags); // accessFlags
		fieldIdx += lastidx;
		char *name = getstr (bin, fields[fieldIdx].name_id); //fields[lastidx].name_id);
		class_name = r_str_replace (class_name, "method.", "", 0);
		class_name = r_str_replace (class_name, ";", "_", 0);
		if (accessFlags == 0) {
		//	eprintf ("PUBLIC\n");
		}
		//eprintf ("f sym.%s.field.%d_%s = %d\n", class_name, i, name, fieldOffset);
		{
			RBinSymbol *sym = R_NEW0 (RBinSymbol);
			sym->name = r_str_newf ("%s.field.%d_%s", class_name, i, name);
			sym->type = r_str_const ("FIELD");
			sym->paddr = sym->vaddr = fieldOffset;
			r_list_append (bin->methods_list, sym);
		}
		lastidx = fieldIdx;
	}
	/* direct methods (aka static) */
	dprintf ("  direct methods: %u\n", (ut32)DM);
	ut64 omi = 0;
	for (i = 0; i < DM; i++) {
		char *method_name, *flag_name;
		ut64 MI, MA, MC;
		p = r_uleb128 (p, p_end - p, &MI);
		MI += omi;
		omi = MI;
		// the mi is diff
#if 0
		index into the method_ids list for the identity of this method (includes the name and descriptor), represented as a difference from the index of previous element in the list. The index of the first element in a list is represented directly.
#endif
		p = r_uleb128 (p, p_end - p, &MA);
		p = r_uleb128 (p, p_end - p, &MC);

		if (MI<bin->header.method_size) methods[MI] = 1;
		if (MC > 0 && bin->code_from > MC) {
			bin->code_from = MC;
		}
		if (MC > 0 && bin->code_to < MC) {
			bin->code_to = MC;
		}

		method_name = dex_method_name (bin, MI);
		dprintf ("METHOD NAME %u\n", (ut32)MI);
		if (!method_name) {
			method_name = strdup ("unknown");
		}
		flag_name = flagname (class_name, method_name);
		if (!flag_name) {
			continue;
		}
		dprintf ("f %s @ 0x%x\n", flag_name, (ut32)MC);
		dprintf ("    { name: %d %d %s,\n", (ut32)MC, (ut32)MI, method_name);
		dprintf ("      idx: %u,\n", (ut32)MI);
		dprintf ("      access_flags: 0x%x,\n", (ut32)MA);
		dprintf ("      code_offset: 0x%x },\n", (ut32)MC);
		/* add symbol */
		if (*flag_name) {
			RBinSymbol *sym = R_NEW0 (RBinSymbol);
			sym->name = flag_name;
			sym->type = r_str_const ("FUNC");
			sym->paddr = sym->vaddr = MC;
			if (MC > 0) { /* avoid methods at 0 paddr */
#if 0
				// TODO: use sdb+pf to show method header
				ut16 regsz;
				ut16 ins_size
				ut16 outs_size
				ut16 tries_size
				ut32 debug_info_off
				ut32 insns_size
				ut16[insn_size] insns;
				ut16 padding = 0
				try_item[tries_size] tries
				encoded_catch_handler_list handlers
#endif
				sym->paddr += 0x10;
				r_list_append (bin->methods_list, sym);
// this causes an invalid flag name issue
				if (cls) {
					if (!cls->methods) {
						/* probably unnecessary for */
						cls->methods = r_list_new ();
					}
					r_list_append (cls->methods, sym);
				}
				/* cache in sdb */
				if (!mdb) {
					mdb = sdb_new0 ();
				}
				sdb_num_set (mdb, sdb_fmt (0, "method.%d", MI), sym->paddr, 0);
			} else {
				//r_list_append (bin->methods_list, sym);
				// XXX memleak sym
				free (sym);
			}
		} else {
			free (flag_name);
		}
		free (method_name);
	}
	/* virtual methods */
	dprintf ("  virtual methods: %u\n", (ut32)VM);
	for (i = 0; i < VM; i++) {
		ut64 MI, MA, MC;
		p = r_uleb128 (p, p_end-p, &MI);
		p = r_uleb128 (p, p_end-p, &MA);
		p = r_uleb128 (p, p_end-p, &MC);

		if (MI < bin->header.method_size) methods[MI] = 1;
		if (MC > 0 && bin->code_from > MC) bin->code_from = MC;
		if (MC > 0 && bin->code_to < MC) bin->code_to = MC;

		char *name = dex_method_name (bin, MI);
		dprintf ("    method name: %s\n", name);
		dprintf ("    method_idx: %u\n", (ut32)MI);
		dprintf ("    method access_flags: %u\n", (ut32)MA);
		dprintf ("    method code_offset: %u\n", (ut32)MC);
		{
			RBinSymbol *sym = R_NEW0 (RBinSymbol);
			//sym->name = r_str_newf ("virtual.%s.%s", class_name, name);
			sym->name = r_str_newf ("%s.%s", class_name, name);
			sym->type = r_str_const ("METH");
			sym->paddr = sym->vaddr = MC;
			r_list_append (bin->methods_list, sym);
		}
		free (name);
	}
	free (class_name);
	return methods;
}

static int dex_loadcode(RBinFile *arch, RBinDexObj *bin) {
	int i;
	int *methods = NULL;

	// doublecheck??
	if (!bin || bin->methods_list) {
		return false;
	}
	bin->code_from = UT64_MAX;
	bin->code_to = 0;
	bin->methods_list = r_list_new ();
	bin->methods_list->free = free;
	bin->imports_list = r_list_new ();
	bin->imports_list->free = free;

	if (bin->header.method_size>bin->size) {
		bin->header.method_size = 0;
		return false;
	}

	/* WrapDown the header sizes to avoid huge allocations */
	bin->header.method_size = R_MIN (bin->header.method_size, bin->size);
	bin->header.class_size = R_MIN (bin->header.class_size, bin->size);
	bin->header.strings_size = R_MIN (bin->header.strings_size, bin->size);

	if (bin->header.strings_size > bin->size) {
		eprintf ("Invalid strings size\n");
		return false;
	}

	dprintf ("Walking %d classes\n", bin->header.class_size);
	if (bin->classes) {
		for (i = 0; i < bin->header.class_size; i++) {
			char *super_name, *class_name;
			struct dex_class_t *c = &bin->classes[i];
			class_name = dex_class_name (bin, c);
			super_name = dex_class_super_name (bin, c);
//eprintf ("%s\n", class_name);
			dprintf ("{\n");
			dprintf ("  class: 0x%x,\n", c->class_id); // indexed by ordinal
			dprintf ("  super: \"%s\",\n", super_name); // indexed by name
			dprintf ("  name: \"%s\",\n", class_name);
			dprintf ("  methods: [\n");
	// sdb_queryf ("(-1)classes=%s", class_name)
	// sdb_queryf ("class.%s.super=%s", super_name)
	// sdb_queryf ("class.%s.methods=%d", class_name, DM);
	#if 0
			if (c->class_data_offset == 0) {
				eprintf ("Skip class\n");
				continue;
			}
	#endif
			free (methods);
			methods = parse_class (arch, bin, c, NULL);
			dprintf ("  ],\n");
			dprintf ("},");
			free (class_name);
			free (super_name);
		}
	}
	if (methods) {
		dprintf ("imports: \n");
		for (i = 0; i < bin->header.method_size; i++) {
			//RBinDexMethod *method = &bin->methods[i];
			if (methods[i]) {
#if 0
				struct dex_class_t *c = &bin->classes[i];
				char *class_name = dex_class_name (bin, c);
				char *method_name = dex_method_name (bin, i);
				// dprintf ("java %s %s\n", class_name, method_name);
				free (class_name);
				free (method_name);
#endif
				continue;
			}
			if (i >= bin->header.class_size) {
				continue;
			}
			struct dex_class_t *c = &bin->classes[i];
			char *class_name = dex_class_name (bin, c);
			if (class_name) {
				free (class_name);
				continue;
			}
			char *method_name = dex_method_name (bin, i);
			eprintf ("import %d (%s  %s)\n", i, class_name, method_name);
			if (method_name && *method_name) {
				RBinSymbol *sym = R_NEW0 (RBinSymbol);
				sym->name = r_str_newf ("imp.%s", method_name);
				sym->vaddr = sym->paddr = i; // ordinal
				sym->type = r_str_const ("IMPORT");
				r_list_append (bin->methods_list, sym);

				RBinImport *imp = R_NEW0 (RBinImport);
#if DEADCODE
				if (class_name) {
					imp->name = r_str_newf ("%s.%s", class_name, method_name);
					eprintf ("ERROR\n");
				}
#endif
				imp->name = r_str_newf ("imp.%s", method_name);
				// TODO: filter more chars that can be trash
				r_str_replace_char (imp->name, ';', '_');
				imp->type = r_str_const ("FUNC");
				imp->ordinal = i;
				imp->bind = "NONE";
				//imp->paddr /= imp->vaddr = 0; // UNKNOWN
				r_list_append (bin->imports_list, imp);
			}
			free (method_name);
			free (class_name);
		}
		free (methods);
	}
	return true;
}

static RList* imports (RBinFile *arch) {
	RBinDexObj *bin = (RBinDexObj*) arch->o->bin_obj;
	if (!bin) {
		return NULL;
	}
	if (bin && bin->imports_list) {
		return bin->imports_list;
	}
	dex_loadcode (arch, bin);
	return bin->imports_list;
#if 0
	struct r_bin_dex_obj_t *bin = (struct r_bin_dex_obj_t *) arch->o->bin_obj;
	int i;
	RList *ret = NULL;
	RBinImport *ptr;

	if (!(ret = r_list_new ()))
		return NULL;
	ret->free = free;
	dprintf ("Importing %d methods... \n", bin->header.method_size);
	for (i = 0; i<bin->header.method_size; i++) {
		if (!(ptr = R_NEW (RBinImport)))
			break;
		char *methodname = get_string (bin, bin->methods[i].name_id);
		char *classname = get_string (bin, bin->methods[i].class_id);
		//char *typename = get_string (bin, bin->methods[i].type_id);
dprintf ("----> %d\n", bin->methods[i].name_id);

		if (!methodname) {
			dprintf ("string index out of range\n");
			break;
		}
		snprintf (ptr->name, sizeof (ptr->name), "import.%s.%s",
				classname, methodname);
		ptr->ordinal = i+1;
		ptr->size = 0;
		ptr->vaddr = ptr->offset = getmethodoffset (bin,
			(int)ptr->ordinal, (ut32*)&ptr->size);
dprintf ("____%s__%s____  (%d)  %llx\n", classname,
	methodname, bin->methods[i].name_id, ptr->vaddr);
free (classname);
free (methodname);
		//strncpy (ptr->forwarder, "NONE", R_BIN_SIZEOF_STRINGS);
		strncpy (ptr->bind, "NONE", R_BIN_SIZEOF_STRINGS);
		if (ptr->vaddr) {
			free (ptr);
			continue;
		}
		strncpy (ptr->type, "IMPORT", R_BIN_SIZEOF_STRINGS);
		r_list_append (ret, ptr);
	}
	dprintf ("Done\n");
	return ret;
#endif
}

static RList* methods (RBinFile *arch) {
	RBinDexObj *bin;
	if (!arch || !arch->o || !arch->o->bin_obj) {
		return NULL;
	}
	bin = (RBinDexObj*) arch->o->bin_obj;
	if (!bin->methods_list) {
		dex_loadcode (arch, bin);
	}
	return bin->methods_list;
}

// wtf?
static void __r_bin_class_free(RBinClass *p) {
	r_bin_class_free (p);
}

static RList* classes (RBinFile *arch) {
	struct r_bin_dex_obj_t *bin;
	struct dex_class_t entry;
	int i, class_index = 0;
	RList *ret = NULL;
	RBinClass *class;
	char name[128];

	if (!arch || !arch->o || !arch->o->bin_obj) {
		return NULL;
	}
	bin = (struct r_bin_dex_obj_t *) arch->o->bin_obj;
	if (bin->header.class_size > bin->size) {
		eprintf ("Too many classes %d\n", bin->header.class_size);
		return NULL;
	}
	if (!(ret = r_list_new ())) {
		return NULL;
	}
	ret->free = (RListFree)__r_bin_class_free;
	for (i = 0; i < bin->header.class_size; i++) {
		ut64 class_addr = (ut64) bin->header.class_offset \
			+ (sizeof (struct dex_class_t)*i);
		// ETOOSLOW
		r_buf_read_at (bin->b, class_addr, (ut8*)&entry,
			sizeof (struct dex_class_t));
		// TODO: implement sections.. each section specifies a class boundary
		// lazy check
		if (!bin->strings) {
			// no bin->strings found
			break;
		}
		if (entry.source_file >= bin->size) {
			continue;
		}
		// unsigned if (entry.source_file<0 || entry.source_file >= bin->header.strings_size)
		if (entry.source_file >= bin->header.strings_size) {
			continue;
		}
		if (bin->strings[entry.source_file] > bin->size) {
			continue;
		}
		r_buf_read_at (bin->b, bin->strings[entry.source_file], (ut8*)name, sizeof (name));
		//snprintf (ptr->name, sizeof (ptr->name), "field.%s.%d", name, i);
		class = R_NEW0 (RBinClass);
		// get source file name (ClassName.java)
		// TODO: use RConstr here
		//class->name = strdup (name[0]<0x41? name+1: name);
		class->name = dex_class_name_byid (bin, entry.class_id);
		// find reference to this class instance
		char *cn = getClassName (class->name);
		if (cn) {
			free (class->name);
			class->index = class_index++;
			class->addr = entry.class_id + bin->header.class_offset;
			class->name = cn;
			//class->addr = class_addr;

			free (parse_class (arch, bin, &entry, class));

			r_list_append (ret, class);
			dprintf ("class.%s=%d\n", name[0]==12?name+1:name, entry.class_id);
			dprintf ("# access_flags = %x;\n", entry.access_flags);
			dprintf ("# super_class = %d;\n", entry.super_class);
			dprintf ("# interfaces_offset = %08x;\n", entry.interfaces_offset);
			//dprintf ("ut32 source_file = %08x;\n", entry.source_file);
			dprintf ("# anotations_offset = %08x;\n", entry.anotations_offset);
			dprintf ("# class_data_offset = %08x;\n", entry.class_data_offset);
			dprintf ("# static_values_offset = %08x;\n\n", entry.static_values_offset);
		} else {
			free (class->name);
			free (class);
		}
	}
	return ret;
}

static int already_entry (RList *entries, ut64 vaddr) {
	RBinAddr *e;
	RListIter *iter;
	r_list_foreach (entries, iter, e) {
		if (e->vaddr == vaddr)
			return 1;
	}
	return 0;
}

static RList* entries(RBinFile *arch) {
	RListIter *iter;
	RBinDexObj *bin;
	RBinSymbol *m;
	RBinAddr *ptr;
	RList *ret;

	if (!arch || !arch->o || !arch->o->bin_obj) {
		return NULL;
	}
	bin = (RBinDexObj*) arch->o->bin_obj;
	ret = r_list_new ();
	ptr = R_NEW0 (RBinAddr);

	if (!bin->methods_list) {
		dex_loadcode (arch, bin);
	}
	// XXX: entry + main???
	r_list_foreach (bin->methods_list, iter, m) {
		if (strlen (m->name)>=4 && !strcmp (m->name+strlen (m->name)-4, "main")) {
			dprintf ("ENTRY -> %s\n", m->name);
			ptr->paddr = ptr->vaddr = m->paddr;
			if (!already_entry (ret, ptr->vaddr)) {
				r_list_append (ret, ptr);
				if (!(ptr = R_NEW0 (RBinAddr))) {
					break;
				}
			}
		}
	}
	if (ptr && r_list_empty (ret)) {
		ptr->paddr = ptr->vaddr = bin->code_from;
		if (!already_entry (ret, ptr->vaddr)) {
			r_list_append (ret, ptr);
		}
	}
	return ret;
}

static ut64 offset_of_method_idx(RBinFile *arch, struct r_bin_dex_obj_t *dex, int idx) {
	int off = dex->header.method_offset + idx;
	//(sizeof (struct dex_method_t)*idx);
	//const char *name = dex_method_name (dex, idx);
	//eprintf ("idx=%d off=%d (%s)\n", idx, off, name);
	//off = sdb_num_get (mdb, name, NULL);
	off = sdb_num_get (mdb, sdb_fmt (0, "method.%d", idx), 0);
	//p = r_uleb128 (p, p_end-p, &MI);
	// READ CODE
	return off;
}

//TODO must return ut64 imho
static int getoffset (RBinFile *arch, int type, int idx) {
	struct r_bin_dex_obj_t *dex = arch->o->bin_obj;
	switch (type) {
	case 'm': // methods
		//if (dex->header.method_size > idx)
		//	return offset_of_method_idx (arch, dex, idx);
		return offset_of_method_idx (arch, dex, idx);
		break;
	case 'c': // class
		if (dex->header.class_size > idx) {
			int off = dex->header.class_offset + idx;
			//(sizeof (struct dex_class_t)*idx);
			//const char *name = dex_class_name_byid (dex, idx);
			//eprintf ("idx=%d off=%d (%s)\n", idx, off, name);
			//p = r_uleb128 (p, p_end-p, &MI);
			return off;
		}
		break;
	case 'f': // fields
		if (dex->header.fields_size > idx) {
			return dex->header.fields_offset +
				(sizeof (struct dex_field_t)*idx);
		}
		break;
	case 'o': // objects
		break;
	case 's': // strings
		if (dex->header.strings_size > idx) {
			if (dex->strings) return dex->strings[idx];
		}
		break;
	case 't': // things
		break;
	}
	return -1;
}

static RList* sections(RBinFile *arch) {
	struct r_bin_dex_obj_t *bin = arch->o->bin_obj;
	RList *ml = methods (arch);
	RBinSection *ptr = NULL;
	int ns, fsymsz = 0;
	RList *ret = NULL;
	RListIter *iter;
	RBinSymbol *m;
	int fsym = 0;

	r_list_foreach (ml, iter, m) {
		if (fsym == 0 || m->paddr<fsym) {
			fsym = m->paddr;
		}
		ns = m->paddr + m->size;
		if (ns > arch->buf->length) {
			continue;
		}
		if (ns > fsymsz) {
			fsymsz = ns;
		}
	}
	if (fsym == 0) {
		return NULL;
	}
	if (!(ret = r_list_new ())) {
		return NULL;
	}
	ret->free = free;

	if ((ptr = R_NEW0 (RBinSection))) {
		strcpy (ptr->name, "header");
		ptr->size = ptr->vsize = sizeof (struct dex_header_t);
		ptr->paddr= ptr->vaddr = 0;
		ptr->srwx = R_BIN_SCN_READABLE | R_BIN_SCN_MAP;
		ptr->add = true;
		r_list_append (ret, ptr);
	}
	if ((ptr = R_NEW0 (RBinSection))) {
		strcpy (ptr->name, "constpool");
		//ptr->size = ptr->vsize = fsym;
		ptr->paddr= ptr->vaddr = sizeof (struct dex_header_t);
		ptr->size = bin->code_from - ptr->vaddr; // fix size
		ptr->srwx = R_BIN_SCN_READABLE | R_BIN_SCN_MAP;
		ptr->add = true;
		r_list_append (ret, ptr);
	}
	if ((ptr = R_NEW0 (RBinSection))) {
		strcpy (ptr->name, "code");
		ptr->vaddr = ptr->paddr = bin->code_from; //ptr->vaddr = fsym;
		ptr->size = bin->code_to - ptr->paddr;
		ptr->srwx = R_BIN_SCN_READABLE | R_BIN_SCN_EXECUTABLE | R_BIN_SCN_MAP;
		ptr->add = true;
		r_list_append (ret, ptr);
	}
	if ((ptr = R_NEW0 (RBinSection))) {
		//ut64 sz = arch ? r_buf_size (arch->buf): 0;
		strcpy (ptr->name, "data");
		ptr->paddr = ptr->vaddr = fsymsz+fsym;
		if (ptr->vaddr > arch->buf->length) {
			ptr->paddr = ptr->vaddr = bin->code_to;
			ptr->size = ptr->vsize = arch->buf->length - ptr->vaddr;
		} else {
			ptr->size = ptr->vsize = arch->buf->length - ptr->vaddr;
			// hacky workaround
			dprintf ("Hack\n");
			//ptr->size = ptr->vsize = 1024;
		}
		ptr->srwx = R_BIN_SCN_READABLE | R_BIN_SCN_MAP; //|2;
		ptr->add = true;
		r_list_append (ret, ptr);
	}
	return ret;
}

static ut64 size(RBinFile *arch) {
	int ret;
	ut32 off = 0, len = 0;
	ret = r_buf_fread_at (arch->buf, 100, (ut8*)&off, "i", 1);
	if (ret != 4) {
		return 0;
	}
	ret = r_buf_fread_at (arch->buf, 104, (ut8*)&len, "i", 1);
	if (ret != 4) {
		return 0;
	}
	return off + len + 0x20;
}

RBinPlugin r_bin_plugin_dex = {
	.name = "dex",
	.desc = "dex format bin plugin",
	.license = "LGPL3",
	.get_sdb = &get_sdb,
	.load = &load,
	.load_bytes = &load_bytes,
	.check = &check,
	.check_bytes = &check_bytes,
	.baddr = &baddr,
	.entries = entries,
	.classes = classes,
	.sections = sections,
	.symbols = methods,
	.imports = imports,
	.strings = strings,
	.info = &info,
	.size = &size,
	.get_offset = &getoffset
};

#ifndef CORELIB
RLibStruct radare_plugin = {
	.type = R_LIB_TYPE_BIN,
	.data = &r_bin_plugin_dex,
	.version = R2_VERSION
};
#endif
