440 lines
15 KiB
C++
440 lines
15 KiB
C++
/*
|
|
* This is a sample plugin module.
|
|
* It demonstrates how to create a graph viewer with an aribtrary graph.
|
|
*
|
|
* It can be compiled by the following compilers:
|
|
*
|
|
* - Borland C++, CBuilder, free C++
|
|
*
|
|
*/
|
|
|
|
#include <ida.hpp>
|
|
#include <idp.hpp>
|
|
#include <graph.hpp>
|
|
#include <loader.hpp>
|
|
#include <kernwin.hpp>
|
|
|
|
//-------------------------------------------------------------------------
|
|
struct plugin_ctx_t;
|
|
struct change_layout_ah_t : public action_handler_t
|
|
{
|
|
plugin_ctx_t &plg;
|
|
change_layout_ah_t(plugin_ctx_t &_plg) : plg(_plg) {}
|
|
virtual int idaapi activate(action_activation_ctx_t *ctx) override;
|
|
virtual action_state_t idaapi update(action_update_ctx_t *ctx) override;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
struct plugin_ctx_t : public plugmod_t, public event_listener_t
|
|
{
|
|
change_layout_ah_t change_layout_ah = change_layout_ah_t(*this);
|
|
const action_desc_t change_layout_desc = ACTION_DESC_LITERAL_PLUGMOD(
|
|
"ugraph:ChangeLayout",
|
|
"User function",
|
|
&change_layout_ah,
|
|
this,
|
|
NULL,
|
|
NULL,
|
|
-1);
|
|
|
|
qstrvec_t graph_text;
|
|
graph_viewer_t *gv = nullptr;
|
|
|
|
plugin_ctx_t()
|
|
{
|
|
hook_event_listener(HT_VIEW, this);
|
|
}
|
|
~plugin_ctx_t()
|
|
{
|
|
// listeners are uninstalled automatically
|
|
// when the owner module is unloaded
|
|
}
|
|
|
|
virtual bool idaapi run(size_t) override;
|
|
virtual ssize_t idaapi on_event(ssize_t code, va_list va) override;
|
|
static ssize_t idaapi gr_callback(void *ud, int code, va_list va);
|
|
};
|
|
|
|
//-------------------------------------------------------------------------
|
|
int idaapi change_layout_ah_t::activate(action_activation_ctx_t *ctx)
|
|
{
|
|
plg.gv = (graph_viewer_t *)ctx->widget;
|
|
mutable_graph_t *g = get_viewer_graph(plg.gv);
|
|
int code = ask_buttons("Circle", "Tree", "Digraph", 1, "Please select layout type");
|
|
node_info_t ni;
|
|
ni.bg_color = 0x44FF55;
|
|
ni.text = "Hello from plugin!";
|
|
set_node_info(g->gid, 7, ni, NIF_BG_COLOR | NIF_TEXT);
|
|
g->current_layout = code + 2;
|
|
g->circle_center = point_t(200, 200);
|
|
g->circle_radius = 200;
|
|
g->redo_layout();
|
|
refresh_viewer(plg.gv);
|
|
return 1;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
action_state_t idaapi change_layout_ah_t::update(action_update_ctx_t *ctx)
|
|
{
|
|
if ( ctx->widget == (TWidget *)plg.gv )
|
|
return AST_ENABLE_FOR_WIDGET;
|
|
else
|
|
return AST_DISABLE_FOR_WIDGET;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static const char *get_node_name(int n)
|
|
{
|
|
switch ( n )
|
|
{
|
|
case 0: return COLSTR("This", SCOLOR_MACRO);
|
|
case 1: return COLSTR("is", SCOLOR_CNAME);
|
|
case 2: return "a";
|
|
case 3: return COLSTR("sample", SCOLOR_DNAME);
|
|
case 4: return COLSTR("graph", SCOLOR_IMPNAME);
|
|
case 5: return COLSTR("viewer", SCOLOR_ERROR);
|
|
case 6: return COLSTR("window!", SCOLOR_DNUM) "\n(with colorful names)";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
ssize_t idaapi plugin_ctx_t::gr_callback(void *ud, int code, va_list va)
|
|
{
|
|
plugin_ctx_t &ctx = *(plugin_ctx_t *)ud;
|
|
ssize_t result = 0;
|
|
switch ( code )
|
|
{
|
|
case grcode_calculating_layout:
|
|
// calculating user-defined graph layout
|
|
// in: mutable_graph_t *g
|
|
// out: 0-not implemented
|
|
// 1-graph layout calculated by the plugin
|
|
msg("calculating graph layout...\n");
|
|
break;
|
|
|
|
case grcode_clicked: // a graph has been clicked
|
|
// in: graph_viewer_t *gv
|
|
// selection_item_t *current_item
|
|
// out: 0-ok, 1-ignore click
|
|
{
|
|
graph_viewer_t *v = va_arg(va, graph_viewer_t *); qnotused(v);
|
|
selection_item_t *it = va_arg(va, selection_item_t *); qnotused(it);
|
|
graph_item_t *m = va_arg(va, graph_item_t *);
|
|
msg("clicked on ");
|
|
switch ( m->type )
|
|
{
|
|
case git_none:
|
|
msg("background\n");
|
|
break;
|
|
case git_edge:
|
|
msg("edge (%d, %d)\n", m->e.src, m->e.dst);
|
|
break;
|
|
case git_node:
|
|
msg("node %d\n", m->n);
|
|
break;
|
|
case git_tool:
|
|
msg("toolbutton %d\n", m->b);
|
|
break;
|
|
case git_text:
|
|
msg("text (x,y)=(%d,%d)\n", m->p.x, m->p.y);
|
|
break;
|
|
case git_elp:
|
|
msg("edge layout point (%d, %d) #%d\n", m->elp.e.src, m->elp.e.dst, m->elp.pidx);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case grcode_dblclicked: // a graph node has been double clicked
|
|
// in: graph_viewer_t *gv
|
|
// selection_item_t *current_item
|
|
// out: 0-ok, 1-ignore click
|
|
{
|
|
graph_viewer_t *v = va_arg(va, graph_viewer_t *);
|
|
selection_item_t *s = va_arg(va, selection_item_t *);
|
|
msg("%p: dblclicked on ", v);
|
|
if ( s == NULL )
|
|
msg("background\n");
|
|
else if ( s->is_node )
|
|
msg("node %d\n", s->node);
|
|
else
|
|
msg("edge (%d, %d) layout point #%d\n", s->elp.e.src, s->elp.e.dst, s->elp.pidx);
|
|
}
|
|
break;
|
|
|
|
case grcode_creating_group:
|
|
// a group is being created
|
|
// in: mutable_graph_t *g
|
|
// intvec_t *nodes
|
|
// out: 0-ok, 1-forbid group creation
|
|
{
|
|
mutable_graph_t *g = va_arg(va, mutable_graph_t *);
|
|
intvec_t &nodes = *va_arg(va, intvec_t *);
|
|
msg("%p: creating group", g);
|
|
for ( intvec_t::iterator p=nodes.begin(); p != nodes.end(); ++p )
|
|
msg(" %d", *p);
|
|
msg("...\n");
|
|
}
|
|
break;
|
|
|
|
case grcode_deleting_group:
|
|
// a group is being deleted
|
|
// in: mutable_graph_t *g
|
|
// int old_group
|
|
// out: 0-ok, 1-forbid group deletion
|
|
{
|
|
mutable_graph_t *g = va_arg(va, mutable_graph_t *);
|
|
int group = va_argi(va, int);
|
|
msg("%p: deleting group %d\n", g, group);
|
|
}
|
|
break;
|
|
|
|
case grcode_group_visibility:
|
|
// a group is being collapsed/uncollapsed
|
|
// in: mutable_graph_t *g
|
|
// int group
|
|
// bool expand
|
|
// out: 0-ok, 1-forbid group modification
|
|
{
|
|
mutable_graph_t *g = va_arg(va, mutable_graph_t *);
|
|
int group = va_argi(va, int);
|
|
bool expand = va_argi(va, bool);
|
|
msg("%p: %scollapsing group %d\n", g, expand ? "un" : "", group);
|
|
}
|
|
break;
|
|
|
|
case grcode_gotfocus: // a graph viewer got focus
|
|
// in: graph_viewer_t *gv
|
|
// out: must return 0
|
|
{
|
|
graph_viewer_t *g = va_arg(va, graph_viewer_t *);
|
|
msg("%p: got focus\n", g);
|
|
}
|
|
break;
|
|
|
|
case grcode_lostfocus: // a graph viewer lost focus
|
|
// in: graph_viewer_t *gv
|
|
// out: must return 0
|
|
{
|
|
graph_viewer_t *g = va_arg(va, graph_viewer_t *);
|
|
msg("%p: lost focus\n", g);
|
|
}
|
|
break;
|
|
|
|
case grcode_user_refresh: // refresh user-defined graph nodes and edges
|
|
// in: mutable_graph_t *g
|
|
// out: success
|
|
{
|
|
mutable_graph_t *g = va_arg(va, mutable_graph_t *);
|
|
msg("%p: refresh\n", g);
|
|
// our graph is like this:
|
|
// 0 -> 1 -> 2
|
|
// \-> 3 -> 4 -> 5 -> 6
|
|
// ^ /
|
|
// \-------/
|
|
if ( g->empty() )
|
|
g->resize(7);
|
|
g->add_edge(0, 1, NULL);
|
|
g->add_edge(1, 2, NULL);
|
|
g->add_edge(1, 3, NULL);
|
|
g->add_edge(3, 4, NULL);
|
|
g->add_edge(4, 5, NULL);
|
|
g->add_edge(5, 3, NULL);
|
|
g->add_edge(5, 6, NULL);
|
|
result = true;
|
|
}
|
|
break;
|
|
|
|
case grcode_user_gentext: // generate text for user-defined graph nodes
|
|
// in: mutable_graph_t *g
|
|
// out: must return 0
|
|
{
|
|
mutable_graph_t *g = va_arg(va, mutable_graph_t *);
|
|
msg("%p: generate text for graph nodes\n", g);
|
|
ctx.graph_text.resize(g->size());
|
|
for ( node_iterator p=g->begin(); p != g->end(); ++p )
|
|
{
|
|
int n = *p;
|
|
ctx.graph_text[n] = get_node_name(n);
|
|
}
|
|
result = true;
|
|
}
|
|
break;
|
|
|
|
case grcode_user_text: // retrieve text for user-defined graph node
|
|
// in: 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!
|
|
{
|
|
mutable_graph_t *g = 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 *);
|
|
*text = ctx.graph_text[node].c_str();
|
|
if ( bgcolor != NULL )
|
|
*bgcolor = DEFCOLOR;
|
|
result = true;
|
|
qnotused(g);
|
|
}
|
|
break;
|
|
|
|
|
|
case grcode_user_size: // calculate node size for user-defined graph
|
|
// in: mutable_graph_t *g
|
|
// int node
|
|
// int *cx
|
|
// int *cy
|
|
// out: 0-did not calculate, ida will use node text size
|
|
// 1-calculated. ida will add node title to the size
|
|
msg("calc node size - not implemented\n");
|
|
// ida will calculate the node size based on the node text
|
|
break;
|
|
|
|
case grcode_user_title: // render node title of a user-defined graph
|
|
// in: mutable_graph_t *g
|
|
// int node
|
|
// rect_t *title_rect
|
|
// int title_bg_color
|
|
// HDC dc
|
|
// out: 0-did not render, ida will fill it with title_bg_color
|
|
// 1-rendered node title
|
|
// ida will draw the node title itself
|
|
break;
|
|
|
|
case grcode_user_draw: // render node of a user-defined graph
|
|
// in: mutable_graph_t *g
|
|
// int node
|
|
// rect_t *node_rect
|
|
// HDC dc
|
|
// out: 0-not rendered, 1-rendered
|
|
// NB: draw only on the specified DC and nowhere else!
|
|
// ida will draw the node text itself
|
|
break;
|
|
|
|
case grcode_user_hint: // retrieve hint for the user-defined graph
|
|
// in: mutable_graph_t *g
|
|
// int mousenode
|
|
// int mouseedge_src
|
|
// int mouseedge_dst
|
|
// char **hint
|
|
// 'hint' must be allocated by qalloc() or qstrdup()
|
|
// out: 0-use default hint, 1-use proposed hint
|
|
{
|
|
mutable_graph_t *g = va_arg(va, mutable_graph_t *);
|
|
int mousenode = va_argi(va, int);
|
|
int mouseedge_src = va_argi(va, int);
|
|
int mouseedge_dst = va_argi(va, int);
|
|
char **hint = va_arg(va, char **);
|
|
char buf[MAXSTR];
|
|
buf[0] = '\0';
|
|
if ( mousenode != -1 )
|
|
qsnprintf(buf, sizeof(buf), "My fancy hint for node %d", mousenode);
|
|
else if ( mouseedge_src != -1 )
|
|
qsnprintf(buf, sizeof(buf), "Hovering on (%d,%d)", mouseedge_src, mouseedge_dst);
|
|
if ( buf[0] != '\0' )
|
|
*hint = qstrdup(buf);
|
|
result = true; // use our hint
|
|
qnotused(g);
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
ssize_t idaapi plugin_ctx_t::on_event(ssize_t code, va_list va)
|
|
{
|
|
if ( code == view_close )
|
|
{
|
|
TWidget *view = va_arg(va, TWidget *);
|
|
if ( view == (TWidget *)gv )
|
|
gv = nullptr;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static plugmod_t *idaapi init()
|
|
{
|
|
if ( !is_idaq() )
|
|
return nullptr;
|
|
return new plugin_ctx_t;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static const char wanted_title[] = "Sample graph";
|
|
bool idaapi plugin_ctx_t::run(size_t)
|
|
{
|
|
TWidget *widget = find_widget(wanted_title);
|
|
if ( widget == nullptr )
|
|
{
|
|
// get a unique graph id
|
|
netnode id;
|
|
id.create("$ ugraph sample");
|
|
gv = create_graph_viewer(wanted_title, id, gr_callback, this, 0);
|
|
if ( gv != nullptr )
|
|
{
|
|
display_widget(gv, WOPN_DP_TAB);
|
|
viewer_fit_window(gv);
|
|
register_action(change_layout_desc);
|
|
viewer_attach_menu_item(gv, change_layout_desc.name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
close_widget(widget, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static const char comment[] = "This is a sample graph plugin.";
|
|
|
|
static const char help[] =
|
|
"A sample graph plugin module\n"
|
|
"\n"
|
|
"This module shows you how to create a graph viewer.";
|
|
|
|
//--------------------------------------------------------------------------
|
|
// This is the preferred name of the plugin module in the menu system
|
|
// The preferred name may be overridden in plugins.cfg file
|
|
|
|
static const char wanted_name[] = "Create sample graph view";
|
|
|
|
|
|
// This is the preferred hotkey for the plugin module
|
|
// The preferred hotkey may be overridden in plugins.cfg file
|
|
|
|
static const char wanted_hotkey[] = "";
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
// PLUGIN DESCRIPTION BLOCK
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
plugin_t PLUGIN =
|
|
{
|
|
IDP_INTERFACE_VERSION,
|
|
PLUGIN_MULTI, // The plugin can work with multiple idbs in parallel
|
|
init, // initialize
|
|
|
|
nullptr,
|
|
|
|
nullptr, // invoke plugin
|
|
|
|
comment, // long comment about the plugin
|
|
// it could appear in the status line
|
|
// or as a hint
|
|
|
|
help, // multiline help about the plugin
|
|
|
|
wanted_name, // the preferred short name of the plugin
|
|
wanted_hotkey // the preferred hotkey to run the plugin
|
|
};
|