src/HexRaysCodeXplorer/CodeXplorer.cpp
/* Copyright (c) 2013-2020
REhints <info@rehints.com>
All rights reserved.
==============================================================================
This file is part of HexRaysCodeXplorer
HexRaysCodeXplorer is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see
<http://www.gnu.org/licenses/>.
==============================================================================
*/
#include "Common.h"
#include "MicrocodeExtractor.h"
#include "CtreeGraphBuilder.h"
#include "ObjectExplorer.h"
#include "TypeReconstructor.h"
#include "TypeExtractor.h"
#include "CtreeExtractor.h"
#include "Utility.h"
#include "Debug.h"
#include "IObjectFormatParser.h"
#include "MSVCObjectFormatParser.h"
#include "GCCObjectFormatParser.h"
#include "ReconstructableType.h"
#include "reconstructed_place_t.h"
#include <functional>
#include <utility>
extern plugin_t PLUGIN;
reconstructed_place_t replace_template;
int g_replace_id;
IObjectFormatParser *object_format_parser = nullptr;
#if defined (__LINUX__) || defined (__MAC__)
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if IDA_SDK_VERSION < 760
// Hex-Rays API pointer
hexdsp_t *hexdsp = nullptr;
#endif
namespace {
static bool inited = false;
// Hotkey for the new command
static char hotkey_dg[] = "Ctrl-Alt-T";
static char hotkey_ce[] = "O";
static char hotkey_rt[] = "R";
static char hotkey_gd[] = "J";
static char hotkey_et[] = "S";
static char hotkey_ec[] = "C";
static char hotkey_vc[] = "V";
static char hotkey_mc[] = "Ctrl-Shift-M";
static char hotkey_so[] = "Q"; // After positioning cursor at source code user can press Q to copy to clipboard string of form modulename + 0xoffset.
// It can be useful while working with WinDbg.
static char hotkey_rv[] = "E"; // Automatic renaming of duplicating variables by pressing E.
// All duplicating successors obtain _2, _3 ... postfixes.
static qstring k_crypto_prefix_param = "CRYPTO";
//--------------------------------------------------------------------------
// Helper class to build graph from ctree.
struct graph_builder_t final : public ctree_parentee_t
{
callgraph_t &cg;
std::map<citem_t *, int> reverse; // Reverse mapping for tests and adding edges
explicit graph_builder_t(callgraph_t &cg) : cg(cg) {}
// overriding functions
int add_node(citem_t *i);
int process(citem_t *i);
// We treat expressions and statements the same way: add them to the graph
int idaapi visit_insn(cinsn_t *i) override { return process(i); }
int idaapi visit_expr(cexpr_t *e) override { return process(e); }
};
// Add a new node to the graph
int graph_builder_t::add_node(citem_t *i)
{
// Check if the item has already been encountered during the traversal
if (reverse.find(i) != reverse.end())
{
warning("bad ctree - duplicate nodes!");
logmsg(DEBUG, "bad ctree - duplicate nodes!");
return -1;
}
// Add a node to the graph
const auto n = cg.add(i);
// Also remember the reverse mapping (citem_t* -> n)
reverse[i] = n;
return n;
}
// Process a ctree item
int graph_builder_t::process(citem_t *i)
{
// Add a node for citem
const auto n = add_node(i);
if (n == -1)
return -1; // error
if (parents.size() > 1) // The current item has a parent?
{
const auto p = reverse[parents.back()]; // Parent node number
// cg.add_edge(p, n); // Add edge from the parent to the current item
cg.create_edge(p, n);
}
return 0;
}
typedef map<lvar_t*, qstring> to_raname_t;
const qstring k_root = "*&|";
struct ida_local renamer_t final : public ctree_visitor_t
{
to_raname_t& to_rename_;
lvars_t& lvars_;
renamer_t(to_raname_t& to_rename, lvars_t& lvars)
: ctree_visitor_t(CV_FAST)
, to_rename_(to_rename)
, lvars_(lvars)
{
}
map<qstring, int> valid_rvars;
map<qstring, int> postfixes;
map<qstring, vector<qstring>> roots;
qstring rvar_depends_on(cexpr_t* e) {
const auto rvar_name = lvars_[e->y->v.idx].name;
for (auto it = roots.begin(); it != roots.end(); ++it)
{
if (it->first == rvar_name)
return k_root;
vector<qstring>::iterator yt;
for (yt = it->second.begin(); yt != it->second.end(); ++yt)
{
if (*yt == rvar_name)
return it->first;
}
}
return k_root;
}
int idaapi visit_expr(cexpr_t *e) override
{
char pstx_buf[8];
if (e->op == cot_asg && e->x->op == cot_var && e->y->op == cot_var)
{
const auto lvar_name = lvars_[e->x->v.idx].name;
auto rvar_name = lvars_[e->y->v.idx].name;
const auto tvar_name = rvar_depends_on(e);
if (tvar_name == k_root)
{
//rvar is root variable
if (rvar_name != lvar_name)
roots[rvar_name].push_back(lvar_name);
}
else
{
//rvar is dependant
if (tvar_name != lvar_name)
{
rvar_name = tvar_name;
roots[tvar_name].push_back(lvar_name);
}
}
for (auto& i : roots[lvar_name])
{
if (i == rvar_name)
return 0;
}
postfixes.insert(pair<qstring, int>(rvar_name, 2));
sprintf(pstx_buf, "%d", postfixes[rvar_name]++);
const auto new_name = rvar_name + "_" + pstx_buf;
to_rename_[&lvars_[e->x->v.idx]] = new_name;
roots[rvar_name].push_back(new_name);
}
return 0;
}
};
} // anonymous
#define DECLARE_GI_VAR \
graph_info_t *gi = (graph_info_t *) ud
#define DECLARE_GI_VARS \
DECLARE_GI_VAR; \
callgraph_t *fg = &gi->fg
//--------------------------------------------------------------------------
static ssize_t idaapi gr_callback(void *ud, const int code, va_list va)
{
auto 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;
const auto f = get_func(gi->func_ea);
if (f == nullptr)
break;
graph_builder_t gb(*fg); // Graph builder helper class
gb.apply_to(&gi->vu->cfunc->body, nullptr);
auto mg = va_arg(va, mutable_graph_t *);
// we have to resize
mg->resize(fg->count());
const auto end = fg->end_edges();
for (auto it = fg->begin_edges();
it != end;
++it)
{
mg->add_edge(it->id1, it->id2, nullptr);
}
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 *);
const auto node = va_arg(va, int);
const auto text = va_arg(va, const char **);
const auto bgcolor = va_arg(va, bgcolor_t *);
const auto ni = fg->get_info(node);
result = ni != nullptr;
if (result)
{
*text = ni->name.c_str();
if (bgcolor != nullptr)
*bgcolor = ni->color;
}
}
break;
case grcode_user_hint:
{
DECLARE_GI_VARS;
va_arg(va, mutable_graph_t *);
const auto mousenode = va_argi(va, int);
const auto to = va_argi(va, int);
const auto from = va_argi(va, int);
const auto hint = va_arg(va, char **);
const auto ni = fg->get_info(mousenode);
result = ni != nullptr;
if (result && ni->ea != BADADDR)
{
qstring s;
get_text_disasm(ni->ea, s);
*hint = qstrdup(s.c_str());
}
}
break;
case grcode_dblclicked:
{
DECLARE_GI_VARS;
const auto v = va_arg(va, graph_viewer_t *);
const auto s = va_arg(va, selection_item_t *);
if (s != nullptr) {
const auto ni = fg->get_info(s->node);
result = ni != nullptr;
if (result && s->is_node && ni->ea != BADADDR)
jumpto(ni->ea);
}
}
break;
default: ;
}
return static_cast<int>(result);
}
// Display ctree graph for current decompiled function
static bool idaapi display_ctree_graph(void *ud)
{
auto& vu = *static_cast<vdui_t*>(ud);
// Determine the ctree item to highlight
vu.get_current_item(USE_KEYBOARD);
citem_t *highlight = vu.item.is_citem() ? vu.item.e : nullptr;
const auto gi = graph_info_t::create(vu.cfunc->entry_ea, highlight);
netnode id;
id.create();
const auto title = gi->title;
auto widget = find_widget(title.c_str());
if (widget)
{
warning("Ctree Graph window already open. Switching to it.\n");
logmsg(DEBUG, "Ctree Graph window already open. Switching to it.\n");
activate_widget(widget, true);
return true;
}
widget = create_empty_widget(title.c_str());
gi->vu = static_cast<vdui_t*>(ud);
gi->widget = widget;
gi->gv = create_graph_viewer("ctree", id, gr_callback, gi, 0, widget);
activate_widget(widget, true);
display_widget(widget, WOPN_DP_TAB);
viewer_fit_window(gi->gv);
return true;
}
// Get pointer to func_t by routine name
func_t * get_func_by_name(const char *func_name)
{
#if 0
func_t * result_func = NULL;
size_t func_total = get_func_qty();
if (func_total > 0)
{
char tmp[1024];
for (unsigned int i = 0; i < func_total - 1; i++)
{
func_t * func = getn_func(i);
if (func != NULL)
{
memset(tmp, 0x00, sizeof(tmp));
qstring func_n;
if (get_func_name(&func_n, func->start_ea) > 0)
{
if (!strcmp(func_name, func_n.c_str()))
{
result_func = func;
break;
}
}
}
}
}
return result_func;
#endif // 0
return get_func(get_name_ea(BADADDR, func_name));
}
static bool get_expr_name(citem_t *citem, qstring& rv)
{
if (!citem->is_expr())
return false;
const auto e = static_cast<cexpr_t*>(citem);
// retrieve the name of the routine
print1wrapper(e, &rv, nullptr);
tag_remove(&rv);
return true;
}
static bool idaapi decompile_func(vdui_t &vu)
{
// Determine the ctree item to highlight
vu.get_current_item(USE_KEYBOARD);
citem_t* highlight = vu.item.is_citem() ? vu.item.e : nullptr;
if (!highlight)
return false;
// if it is an expression
if (!highlight->is_expr())
return false;
auto*e = static_cast<cexpr_t*>(highlight);
qstring qcitem_name;
if (!get_expr_name(highlight, qcitem_name))
return false;
const auto citem_name = qcitem_name.c_str();
auto proc_name = citem_name + strlen(citem_name);
while ((proc_name > citem_name) && (*(proc_name - 1) != '>')) // WTF is going here?
proc_name--;
if (proc_name != citem_name)
{
if (const auto func = get_func_by_name(proc_name))
open_pseudocode(func->start_ea, -1);
}
return true;
}
//--------------------------------------------------------------------------
//
// TODO: Make changes persistent
//
static bool idaapi rename_simple_expr(void *ud)
{
auto& vu = *static_cast<vdui_t*>(ud);
auto pfunc = vu.cfunc;
auto& lvars = *pfunc->get_lvars();
map<lvar_t*, qstring> to_rename;
renamer_t zc{to_rename, lvars};
zc.apply_to(&pfunc->body, nullptr);
for (auto it = to_rename.begin(); it != to_rename.end(); ++it)
vu.rename_lvar(it->first, it->second.c_str(), false);
vu.refresh_ctext();
return true;
}
//--------------------------------------------------------------------------
static bool idaapi show_offset_in_windbg_format(void *ud) {
char _offset[32] = { 0 };
char module_name[256] = { 0 };
qstring result;
auto& vu = *static_cast<vdui_t*>(ud);
vu.get_current_item(USE_KEYBOARD);
const adiff_t offset = vu.item.i->ea - get_imagebase();
if (offset < 0)
{
info("Locate pointer after = sign or at operand of function\n");
return false;
}
get_root_filename(module_name, 255);
for (auto i = 0; i < 255; i++)
if (module_name[i] == '.') { module_name[i] = 0; break; }
#ifdef __EA64__
const auto fmt = "%llx";
#else
const auto fmt = "%x";
#endif
sprintf(_offset, fmt, offset);
result.cat_sprnt("%s+0x%s", module_name, _offset);
qstring title;
title.cat_sprnt("0x%X", vu.item.i->ea);
show_string_in_custom_view(&vu, title, result);
#if defined (__LINUX__) || defined (__MAC__)
msg("%s", result.c_str());
#else
OpenClipboard(0);
EmptyClipboard();
const auto hg = GlobalAlloc(GMEM_MOVEABLE, result.size());
if (!hg)
{
CloseClipboard();
msg("Can't alloc\n");
return false;
}
CopyMemory(GlobalLock(hg), result.c_str(), result.size());
GlobalUnlock(hg);
SetClipboardData(CF_TEXT, hg);
CloseClipboard();
GlobalFree(hg);
#endif
return true;
}
//--------------------------------------------------------------------------
// show disassembly line for ctree->item
bool idaapi decompiled_line_to_disasm_cb(void *ud)
{
auto& vu = *static_cast<vdui_t*>(ud);
vu.ctree_to_disasm();
vu.get_current_item(USE_KEYBOARD);
citem_t *highlight = vu.item.is_citem() ? vu.item.e : nullptr;
return true;
}
//--------------------------------------------------------------------------
// extract ctree to custom view
static bool idaapi show_current_citem_in_custom_view(void *ud)
{
auto &vu = *static_cast<vdui_t*>(ud);
vu.get_current_item(USE_KEYBOARD);
auto *highlight_item = vu.item.is_citem() ? vu.item.e : nullptr;
if (!highlight_item)
return false;
const ctree_dumper_t ctree_dump;
qstring ctree_item;
ctree_dump.parse_ctree_item(highlight_item, ctree_item);
if (highlight_item->is_expr())
{
auto *e = static_cast<cexpr_t*>(highlight_item);
qstring item_name;
get_expr_name(highlight_item, item_name);
show_citem_custom_view(&vu, ctree_item, item_name);
}
return true;
}
//--------------------------------------------------------------------------
bool init_object_format_parser()
{
if (!object_format_parser)
{
if (compilerIs(MSVC_COMPILER_ABBR))
object_format_parser = new MSVCObjectFormatParser();
if (compilerIs(GCC_COMPILER_ABBR))
object_format_parser = new GCCObjectFormatParser();
if (!object_format_parser)
{
info("CodeXplorer doesn't support yet parsing of neither MSVC nor GCC VTBL's");
return false;
}
}
return true;
}
//--------------------------------------------------------------------------
// display Object Explorer
static bool idaapi display_vtbl_objects(void *ud)
{
if (!init_object_format_parser())
return false;
search_objects();
object_explorer_form_init();
re_types_form_init();
return true;
}
//--------------------------------------------------------------------------
// display Microcode Explorer window
static bool idaapi display_micvrocode_explorer(void* ud)
{
clear_cached_cfuncs();
show_microcode_explorer();
return true;
}
//--------------------------------------------------------------------------
// This callback handles various hexrays events.
static ssize_t idaapi callback(void* ud, const hexrays_event_t event, va_list va)
{
switch (event)
{
case hxe_populating_popup:
{
auto widget = va_arg(va, TWidget *);
const auto popup = va_arg(va, TPopupMenu *);
auto &vu = *va_arg(va, vdui_t *);
// add new command to the popup menu
attach_action_to_popup(vu.ct, popup, "codexplorer::display_ctree_graph");
attach_action_to_popup(vu.ct, popup, "codexplorer::object_explorer");
attach_action_to_popup(vu.ct, popup, "codexplorer::reconstruct_type");
attach_action_to_popup(vu.ct, popup, "codexplorer::extract_types_to_file");
attach_action_to_popup(vu.ct, popup, "codexplorer::extract_ctrees_to_file");
attach_action_to_popup(vu.ct, popup, "codexplorer::ctree_item_view");
attach_action_to_popup(vu.ct, popup, "codexplorer::microcode_view");
attach_action_to_popup(vu.ct, popup, "codexplorer::jump_to_disasm");
attach_action_to_popup(vu.ct, popup, "codexplorer::show_copy_item_offset");
attach_action_to_popup(vu.ct, popup, "codexplorer::rename_vars");
}
break;
case hxe_double_click:
{
auto& vu = *va_arg(va, vdui_t *);
decompile_func(vu);
}
break;
}
return 0;
}
void parse_plugin_options(qstring &options, bool &dump_types, bool &dump_ctrees, qstring &crypto_prefix) {
qvector<qstring> params;
const qstring splitter = ":";
split_qstring(options, splitter, params);
dump_types = false;
dump_ctrees = false;
crypto_prefix = "";
for (const auto& param : params) {
if (param == "dump_types") {
dump_types = true;
}
else if (param == "dump_ctrees") {
dump_ctrees = true;
}
else if (param.length() > k_crypto_prefix_param.length() && param.find(k_crypto_prefix_param) == 0) {
crypto_prefix = param.substr(k_crypto_prefix_param.length());
}
else {
qstring message = "Invalid argument: ";
message += param + "\n";
logmsg(INFO, message.c_str());
}
}
}
namespace {
class menu_action_handler final : public action_handler_t
{
public:
typedef std::function<bool(void*)> handler_t;
bool is_enabled;
explicit menu_action_handler(handler_t handler)
: is_enabled(true), handler_(std::move(handler))
{
}
menu_action_handler(handler_t handler, const bool enabled)
: is_enabled(enabled), handler_(std::move(handler))
{
}
int idaapi activate(action_activation_ctx_t* ctx) override
{
const auto vdui = get_widget_vdui(ctx->widget);
return handler_(vdui) ? TRUE : FALSE;
}
action_state_t idaapi update(action_update_ctx_t* ctx) override
{
return ctx->widget_type == BWN_PSEUDOCODE ? AST_ENABLE_FOR_WIDGET : AST_DISABLE_FOR_WIDGET;
}
private:
handler_t handler_;
};
static menu_action_handler k_display_ctree_graph_handler{ display_ctree_graph };
static menu_action_handler k_object_explorer_handler{ display_vtbl_objects };
static menu_action_handler k_reconstruct_type_handler{ reconstruct_type_cb };
static menu_action_handler k_extract_all_types_handler{ extract_all_types };
static menu_action_handler k_extract_all_ctrees_handler{ extract_all_ctrees };
static menu_action_handler k_show_current_c_item_in_custom_view{ show_current_citem_in_custom_view };
static menu_action_handler k_show_microcode_explorer_view{ display_micvrocode_explorer };
static menu_action_handler k_jump_to_disasm_handler{ decompiled_line_to_disasm_cb };
static menu_action_handler k_show_offset_in_windbg_format_handler{ show_offset_in_windbg_format };
static menu_action_handler k_rename_vars_handler{ rename_simple_expr };
static action_desc_t k_action_descs[] = {
ACTION_DESC_LITERAL("codexplorer::display_ctree_graph", "Display Ctree Graph", &k_display_ctree_graph_handler, hotkey_dg, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::object_explorer", "Object Explorer", &k_object_explorer_handler, hotkey_ce, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::reconstruct_type", "REconstruct Type", &k_reconstruct_type_handler, hotkey_rt, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::extract_types_to_file", "Extract Types to File", &k_extract_all_types_handler, hotkey_et, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::extract_ctrees_to_file", "Extract Ctrees to File", &k_extract_all_ctrees_handler, hotkey_ec, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::ctree_item_view", "Ctree Item View", &k_show_current_c_item_in_custom_view, hotkey_vc, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::microcode_view", "Microcode View", &k_show_microcode_explorer_view, hotkey_mc, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::jump_to_disasm", "Jump to Disasm", &k_jump_to_disasm_handler, hotkey_gd, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::show_copy_item_offset", "Show/Copy item offset", &k_show_offset_in_windbg_format_handler, hotkey_so, nullptr, -1),
ACTION_DESC_LITERAL("codexplorer::rename_vars", "Rename vars", &k_rename_vars_handler, hotkey_rv, nullptr, -1)
};
static const cfgopt_t g_opts[] =
{
cfgopt_t("HOTKEY_DISPLAY_GRAPH", hotkey_dg, (size_t)sizeof(hotkey_dg), false),
cfgopt_t("HOTKEY_OBJECT_EXPLORER", hotkey_ce, (size_t)sizeof(hotkey_ce), false),
cfgopt_t("HOTKEY_RECONSTRUCT_TYPE", hotkey_rt, (size_t)sizeof(hotkey_rt), false),
cfgopt_t("HOTKEY_JUMP_TO_DISASM", hotkey_gd, (size_t)sizeof(hotkey_gd), false),
cfgopt_t("HOTKEY_EXTRACT_TYPES", hotkey_et, (size_t)sizeof(hotkey_et), false),
cfgopt_t("HOTKEY_EXTRACT_CTREE", hotkey_ec, (size_t)sizeof(hotkey_ec), false),
cfgopt_t("HOTKEY_CTREE_EXPLORER", hotkey_vc, (size_t)sizeof(hotkey_vc), false),
cfgopt_t("HOTKEY_MICROCODE_EXPLORER", hotkey_mc, (size_t)sizeof(hotkey_mc), false),
cfgopt_t("HOTKEY_SHOW_COPY_ITEM_OFFSET", hotkey_so, (size_t)sizeof(hotkey_so), false),
cfgopt_t("HOTKEY_RENAME_VARS", hotkey_rv, (size_t)sizeof(hotkey_rv), false)
};
}
// Plugin context, necessary for the correct work of the plugin with the flag PLUGIN_MULTI
struct codexplorer_ctx_t : public plugmod_t
{
~codexplorer_ctx_t()
{
if (inited)
{
logmsg(INFO, "\nHexRaysCodeXplorer plugin by @REhints terminated.\n\n\n");
remove_hexrays_callback(static_cast<hexrays_cb_t*>(callback), nullptr);
re_types_form_fini();
term_hexrays_plugin();
}
}
virtual bool idaapi run(size_t arg) override;
};
bool idaapi codexplorer_ctx_t::run(size_t arg)
{
auto reconstruct_type_params = *reinterpret_cast<reconstruct_type_params_t *>(arg);
return reconstruct_type(reconstruct_type_params);
}
//--------------------------------------------------------------------------
// Initialize the plugin.
plugmod_t *idaapi init(void)
{
logmsg(INFO, "\nHexRaysCodeXplorer plugin by @REhints loaded.\n\n\n");
msg("\nHexRaysCodeXplorer plugin by @REhints loaded.\n\n\n");
if (!init_hexrays_plugin())
{
warning("Hex-Rays Decompiler not found!");
return nullptr; // no decompiler
}
auto dump_types = false;
auto dump_ctrees = false;
qstring crypto_prefix;
qstring options = get_plugin_options(PLUGIN.wanted_name);
parse_plugin_options(options, dump_types, dump_ctrees, crypto_prefix);
auto config_read = read_config_file("codeexplorer.cfg", g_opts, _countof(g_opts), nullptr);
for (auto& k_action_desc : k_action_descs)
{
if (k_action_desc.shortcut[0])
register_action(k_action_desc);
}
install_hexrays_callback(static_cast<hexrays_cb_t*>(callback), nullptr);
logmsg(INFO, "Hex-rays version %s has been detected\n", get_hexrays_version());
inited = true;
if (dump_ctrees || dump_types) {
auto_wait();
if (dump_types) {
logmsg(DEBUG, "Dumping types\n");
extract_all_types(nullptr);
const auto file_id = qcreate("codexplorer_types_done", 511);
if (file_id != -1)
qclose(file_id);
}
if (dump_ctrees) {
logmsg(DEBUG, "Dumping ctrees\n");
dump_funcs_ctree(nullptr, crypto_prefix);
const auto file_id = qcreate("codexplorer_ctrees_done", 511);
if (file_id != -1)
qclose(file_id);
}
logmsg(INFO, "\nHexRaysCodeXplorer plugin by @REhints exiting...\n\n\n");
}
// You should always pass at least PCF_MAKEPLACE_ALLOCATES
g_replace_id = register_place_class(&replace_template, PCF_MAKEPLACE_ALLOCATES, &PLUGIN);
return new codexplorer_ctx_t;
}
//--------------------------------------------------------------------------
static const char comment[] = "HexRaysCodeXplorer plugin by @REhints";
//--------------------------------------------------------------------------
//
// PLUGIN DESCRIPTION BLOCK
//
//--------------------------------------------------------------------------
plugin_t PLUGIN =
{
IDP_INTERFACE_VERSION,
PLUGIN_HIDE | PLUGIN_MULTI, // plugin flags
init, // initialize
nullptr, // terminate. this pointer may be NULL.
nullptr, // invoke plugin
comment, // long comment about the plugin
// it could appear in the status line or as a hint
"", // multiline help about the plugin
"HexRaysCodeXplorer by @REhints", // the preferred short name of the plugin (PLUGIN.wanted_name)
"" // the preferred hotkey to run the plugin
};