BOOL get_vtbl_info(ea_t ea_address, VTBL_info_t &vtbl_info)
{
	flags_t flags = getFlags(ea_address);
	if (!(hasRef(flags) || has_any_name(flags) && (isDwrd(flags) || isUnknown(flags))))
		return(FALSE);
	else
	{
		BOOL is_move_xref = FALSE;
		ea_t ea_code_ref = get_first_dref_to(ea_address);
		if (ea_code_ref && (ea_code_ref != BADADDR))
		{
			do
			{
				if (isCode(getFlags(ea_code_ref)))
				{
					LPCTSTR disasm_line = get_text_disasm(ea_code_ref);
#ifndef __EA64__
					if ((*((PUINT)disasm_line) == 0x20766F6D /*"mov "*/) && (strstr(disasm_line + 4, " offset ") != NULL))
#else
					if ((*((PUINT)disasm_line) == 0x2061656c /*"lea "*/) && (strstr(disasm_line + 4, "rcx") != NULL) && (strstr(disasm_line + 4, "const") != NULL))
#endif
					{
						is_move_xref = TRUE;
						break;
					}
				}

				ea_code_ref = get_next_dref_to(ea_address, ea_code_ref);

			} while (ea_code_ref && (ea_code_ref != BADADDR));
		}
		if (!is_move_xref)
			return(FALSE);

		ZeroMemory(&vtbl_info, sizeof(VTBL_info_t));

		// get_name(BADADDR, ea_address, vtbl_info.vtbl_name, (MAXSTR - 1));
		f_get_ea_name(&vtbl_info.vtbl_name, ea_address);
		ea_t ea_start = vtbl_info.ea_begin = ea_address;
		while (TRUE)
		{
			flags_t index_flags = getFlags(ea_address);
#ifndef __EA64__
			if (!(hasValue(index_flags) && (isDwrd(index_flags) || isUnknown(index_flags))))
#else
			if (!(hasValue(index_flags) && (isQwrd(index_flags) || isUnknown(index_flags)))) 
#endif
				break;
#ifndef __EA64__
			ea_t ea_index_value = get_32bit(ea_address);
#else
			ea_t ea_index_value = get_64bit(ea_address);
#endif

			if (!(ea_index_value && (ea_index_value != BADADDR)))
				break;

			if (ea_address != ea_start)
				if (hasRef(index_flags))
					break;

			flags_t value_flags = getFlags(ea_index_value);
			if (!isCode(value_flags))
				break;
			else
				if (isUnknown(index_flags))
#ifndef __EA64__
					doDwrd(ea_address, sizeof(DWORD));
			ea_address += sizeof(UINT);
#else
					doQwrd(ea_address, sizeof(UINT64));
			ea_address += sizeof(UINT64);
#endif
		};
#ifndef __EA64__
		if ((vtbl_info.methods = ((ea_address - ea_start) / sizeof(UINT))) > 0)
#else
		if((vtbl_info.methods = ((ea_address - ea_start) / sizeof(UINT64))) > 0)
#endif
		{
			vtbl_info.ea_end = ea_address;
			return(TRUE);
		}
		else
			return(FALSE);
	}
}
//--------------------------------------------------------------------------
static int idaapi gr_callback(void *ud, int code, va_list va)
{
	bool result = false;
	switch (code)
	{
		// refresh user-defined graph nodes and edges
	case grcode_user_refresh:
		// in:  mutable_graph_t *g
		// out: success
	{
		DECLARE_GI_VARS;
		func_t *f = get_func(gi->func_ea);
		if (f == NULL)
			break;

		graph_builder_t gb(*fg);       // Graph builder helper class
		gb.apply_to(&gi->vu->cfunc->body, NULL);

		mutable_graph_t *mg = va_arg(va, mutable_graph_t *);

		// we have to resize
		mg->resize(fg->count());

		callgraph_t::edge_iterator end = fg->end_edges();
		for (callgraph_t::edge_iterator it = fg->begin_edges();
			it != end;
			++it)
		{
			mg->add_edge(it->id1, it->id2, NULL);
		}

		fg->clear_edges();
		result = true;
	}
	break;

	// retrieve text for user-defined graph node
	case grcode_user_text:
		//mutable_graph_t *g
		//      int node
		//      const char **result
		//      bgcolor_t *bg_color (maybe NULL)
		// out: must return 0, result must be filled
		// NB: do not use anything calling GDI!
	{
		DECLARE_GI_VARS;
		va_arg(va, mutable_graph_t *);
		int node = va_arg(va, int);
		const char **text = va_arg(va, const char **);
		bgcolor_t *bgcolor = va_arg(va, bgcolor_t *);

		callgraph_t::nodeinfo_t *ni = fg->get_info(node);
		result = ni != NULL;
		if (result)
		{
			*text = ni->name.c_str();
			if (bgcolor != NULL)
				*bgcolor = ni->color;
		}
	}
	break;

	case grcode_user_hint:
	{
		DECLARE_GI_VARS;
		va_arg(va, mutable_graph_t *);
		int mousenode = va_argi(va, int);
		int to = va_argi(va, int);
		int from = va_argi(va, int);
		char **hint = va_arg(va, char **);

		callgraph_t::nodeinfo_t *ni = fg->get_info(mousenode);
		result = ni != NULL;
		if (result && ni->ea != BADADDR)
		{
			qstring s = get_text_disasm(ni->ea);
			*hint = qstrdup(s.c_str());
		}
	}
	break;

	case grcode_dblclicked:
	{
		DECLARE_GI_VARS;
		graph_viewer_t *v = va_arg(va, graph_viewer_t *);
		selection_item_t *s = va_arg(va, selection_item_t *);

		callgraph_t::nodeinfo_t *ni = fg->get_info(s->node);
		result = ni != NULL;
		if (result && s->is_node && ni->ea != BADADDR)
			jumpto(ni->ea);
	}
	break;

	}
	return (int)result;
}