11#include " lua_engine.h"
22#include < fmt/format.h>
33#include < spdlog/spdlog.h>
4+ #include < imgui.h>
45#include < cstring>
6+ #include < algorithm>
57
68extern " C" {
79#include < lua.h>
@@ -13,6 +15,10 @@ namespace hype {
1315
1416namespace {
1517
18+ // -----------------------------------------------------------------------
19+ // Registry helpers
20+ // -----------------------------------------------------------------------
21+
1622LuaEngine* get_engine (lua_State* L) {
1723 lua_getfield (L, LUA_REGISTRYINDEX , " __hype_engine" );
1824 auto * eng = static_cast <LuaEngine*>(lua_touserdata (L, -1 ));
@@ -34,6 +40,76 @@ PEImage* get_img(lua_State* L) {
3440 return img;
3541}
3642
43+ // -----------------------------------------------------------------------
44+ // Hotkey parsing helpers
45+ // -----------------------------------------------------------------------
46+
47+ struct HotkeySpec {
48+ ImGuiKey key = ImGuiKey_None;
49+ bool ctrl = false ;
50+ bool shift = false ;
51+ bool alt = false ;
52+ };
53+
54+ // Parse strings like "Ctrl+Shift+R", "Alt+F7", "F5", "Ctrl+G"
55+ HotkeySpec parse_hotkey (const std::string& s) {
56+ HotkeySpec spec;
57+ std::string token;
58+ std::string src = s;
59+ // Split on '+'
60+ std::vector<std::string> parts;
61+ size_t pos = 0 ;
62+ while ((pos = src.find (' +' )) != std::string::npos) {
63+ parts.push_back (src.substr (0 , pos));
64+ src.erase (0 , pos + 1 );
65+ }
66+ parts.push_back (src); // last part
67+
68+ for (auto & p : parts) {
69+ // to lower for comparison
70+ std::string lp = p;
71+ for (auto & c : lp) c = static_cast <char >(std::tolower (static_cast <unsigned char >(c)));
72+ if (lp == " ctrl" ) { spec.ctrl = true ; continue ; }
73+ if (lp == " shift" ) { spec.shift = true ; continue ; }
74+ if (lp == " alt" ) { spec.alt = true ; continue ; }
75+
76+ // Match ImGuiKey
77+ if (p.size () == 1 ) {
78+ char c = static_cast <char >(std::toupper (static_cast <unsigned char >(p[0 ])));
79+ if (c >= ' A' && c <= ' Z' )
80+ spec.key = static_cast <ImGuiKey>(ImGuiKey_A + (c - ' A' ));
81+ else if (c >= ' 0' && c <= ' 9' )
82+ spec.key = static_cast <ImGuiKey>(ImGuiKey_0 + (c - ' 0' ));
83+ } else if (lp[0 ] == ' f' && lp.size () <= 3 ) {
84+ // F1–F12
85+ int n = 0 ;
86+ try { n = std::stoi (lp.substr (1 )); } catch (...) {}
87+ if (n >= 1 && n <= 12 )
88+ spec.key = static_cast <ImGuiKey>(ImGuiKey_F1 + (n - 1 ));
89+ } else if (lp == " space" ) { spec.key = ImGuiKey_Space; }
90+ else if (lp == " tab" ) { spec.key = ImGuiKey_Tab; }
91+ else if (lp == " enter" ) { spec.key = ImGuiKey_Enter; }
92+ else if (lp == " escape" ) { spec.key = ImGuiKey_Escape; }
93+ else if (lp == " delete" ) { spec.key = ImGuiKey_Delete; }
94+ else if (lp == " home" ) { spec.key = ImGuiKey_Home; }
95+ else if (lp == " end" ) { spec.key = ImGuiKey_End; }
96+ }
97+ return spec;
98+ }
99+
100+ bool check_hotkey (const HotkeySpec& spec) {
101+ if (spec.key == ImGuiKey_None) return false ;
102+ auto & io = ImGui::GetIO ();
103+ if (spec.ctrl != io.KeyCtrl ) return false ;
104+ if (spec.shift != io.KeyShift ) return false ;
105+ if (spec.alt != io.KeyAlt ) return false ;
106+ return ImGui::IsKeyPressed (spec.key );
107+ }
108+
109+ // -----------------------------------------------------------------------
110+ // Existing Lua API
111+ // -----------------------------------------------------------------------
112+
37113int l_get_name (lua_State* L) {
38114 auto * db = get_db (L);
39115 if (!db) { lua_pushnil (L); return 1 ; }
@@ -102,9 +178,9 @@ int l_get_bytes(lua_State* L) {
102178
103179 for (auto & seg : img->segments ) {
104180 if (!seg.contains (addr)) continue ;
105- size_t off = static_cast <size_t >(addr - seg.va );
181+ size_t off = static_cast <size_t >(addr - seg.va );
106182 size_t avail = seg.data .size () - off;
107- size_t n = static_cast <size_t >(len) < avail ? static_cast <size_t >(len) : avail;
183+ size_t n = static_cast <size_t >(len) < avail ? static_cast <size_t >(len) : avail;
108184 lua_pushlstring (L, reinterpret_cast <const char *>(seg.data .data () + off), n);
109185 return 1 ;
110186 }
@@ -178,19 +254,104 @@ int l_goto(lua_State* L) {
178254 return 0 ;
179255}
180256
181- } // anon
257+ // -----------------------------------------------------------------------
258+ // Plugin registration Lua API
259+ // -----------------------------------------------------------------------
260+
261+ int l_register_plugin (lua_State* L) {
262+ auto * eng = get_engine (L);
263+ if (!eng) return 0 ;
264+ const char * name = luaL_optstring (L, 1 , " Unnamed Plugin" );
265+ const char * desc = luaL_optstring (L, 2 , " " );
266+
267+ // Find the entry that was pre-created for the current file, or create one
268+ auto & plugins = const_cast <std::vector<PluginEntry>&>(eng->plugins ());
269+ for (auto & p : plugins) {
270+ if (p.path == eng->current_plugin_name ()) {
271+ p.name = name;
272+ p.desc = desc;
273+ return 0 ;
274+ }
275+ }
276+ // Fallback: shouldn't normally reach here
277+ PluginEntry e;
278+ e.name = name;
279+ e.desc = desc;
280+ e.path = eng->current_plugin_name ();
281+ plugins.push_back (std::move (e));
282+ return 0 ;
283+ }
284+
285+ int l_register_menu_item (lua_State* L) {
286+ auto * eng = get_engine (L);
287+ if (!eng) return 0 ;
288+
289+ const char * label = luaL_checkstring (L, 1 );
290+ luaL_checktype (L, 2 , LUA_TFUNCTION );
291+ lua_pushvalue (L, 2 );
292+ int ref = luaL_ref (L, LUA_REGISTRYINDEX );
293+
294+ auto & plugins = const_cast <std::vector<PluginEntry>&>(eng->plugins ());
295+ // Add to the most recently active plugin
296+ for (auto it = plugins.rbegin (); it != plugins.rend (); ++it) {
297+ if (it->path == eng->current_plugin_name ()) {
298+ it->items .push_back ({label, ref});
299+ return 0 ;
300+ }
301+ }
302+ // If no plugin registered yet, attach to a default entry
303+ if (!plugins.empty ()) {
304+ plugins.back ().items .push_back ({label, ref});
305+ }
306+ return 0 ;
307+ }
308+
309+ int l_register_hotkey (lua_State* L) {
310+ auto * eng = get_engine (L);
311+ if (!eng) return 0 ;
312+ const char * key_str = luaL_checkstring (L, 1 );
313+ luaL_checktype (L, 2 , LUA_TFUNCTION );
314+ lua_pushvalue (L, 2 );
315+ int ref = luaL_ref (L, LUA_REGISTRYINDEX );
316+ eng->add_hotkey (key_str, ref);
317+ return 0 ;
318+ }
319+
320+ int l_register_on_analysis_complete (lua_State* L) {
321+ auto * eng = get_engine (L);
322+ if (!eng) return 0 ;
323+ luaL_checktype (L, 1 , LUA_TFUNCTION );
324+ lua_pushvalue (L, 1 );
325+ int ref = luaL_ref (L, LUA_REGISTRYINDEX );
326+ eng->add_analysis_cb (ref);
327+ return 0 ;
328+ }
329+
330+ } // anon namespace
331+
332+ // -----------------------------------------------------------------------
333+ // LuaEngine implementation
334+ // -----------------------------------------------------------------------
182335
183336LuaEngine::LuaEngine () {
184337 L_ = luaL_newstate ();
185338 luaL_openlibs (L_ );
186339}
187340
188341LuaEngine::~LuaEngine () {
342+ // Release all Lua registry refs before closing
343+ for (auto & p : plugins_)
344+ for (auto & item : p.items )
345+ if (item.cb_ref != LUA_NOREF ) luaL_unref (L_ , LUA_REGISTRYINDEX , item.cb_ref );
346+ for (auto & [_, ref] : hotkeys_)
347+ if (ref != LUA_NOREF ) luaL_unref (L_ , LUA_REGISTRYINDEX , ref);
348+ for (auto ref : on_analysis_cbs_)
349+ if (ref != LUA_NOREF ) luaL_unref (L_ , LUA_REGISTRYINDEX , ref);
189350 if (L_ ) lua_close (L_ );
190351}
191352
192353void LuaEngine::init (AnalysisDB* db, PEImage* img) {
193- db_ = db;
354+ db_ = db;
194355 img_ = img;
195356 register_api ();
196357}
@@ -207,16 +368,147 @@ void LuaEngine::register_api() {
207368 lua_pushlightuserdata (L_ , &nav_cb_);
208369 lua_setfield (L_ , LUA_REGISTRYINDEX , " __hype_nav" );
209370
210- lua_register (L_ , " get_name" , l_get_name);
211- lua_register (L_ , " set_name" , l_set_name);
212- lua_register (L_ , " get_func" , l_get_func);
213- lua_register (L_ , " get_insn" , l_get_insn);
214- lua_register (L_ , " get_bytes" , l_get_bytes);
371+ // Core API
372+ lua_register (L_ , " get_name" , l_get_name);
373+ lua_register (L_ , " set_name" , l_set_name);
374+ lua_register (L_ , " get_func" , l_get_func);
375+ lua_register (L_ , " get_insn" , l_get_insn);
376+ lua_register (L_ , " get_bytes" , l_get_bytes);
215377 lua_register (L_ , " set_comment" , l_set_comment);
216378 lua_register (L_ , " get_xrefs_to" , l_get_xrefs_to);
217379 lua_register (L_ , " get_functions" , l_get_functions);
218- lua_register (L_ , " print" , l_print);
219- lua_register (L_ , " goto_addr" , l_goto);
380+ lua_register (L_ , " print" , l_print);
381+ lua_register (L_ , " goto_addr" , l_goto);
382+
383+ // Plugin registration API
384+ lua_register (L_ , " register_plugin" , l_register_plugin);
385+ lua_register (L_ , " register_menu_item" , l_register_menu_item);
386+ lua_register (L_ , " register_hotkey" , l_register_hotkey);
387+ lua_register (L_ , " register_on_analysis_complete" , l_register_on_analysis_complete);
388+ }
389+
390+ // Register engine pointer and api before analysis (plugins load pre-loop).
391+ // We call register_api early so plugins can call register_* at load time
392+ // even though db_ and img_ are still null — API functions guard for null.
393+ void LuaEngine::load_plugins (const std::filesystem::path& dir) {
394+ // Register the engine pointer early so register_* functions work
395+ lua_pushlightuserdata (L_ , this );
396+ lua_setfield (L_ , LUA_REGISTRYINDEX , " __hype_engine" );
397+ lua_pushlightuserdata (L_ , &output_);
398+ lua_setfield (L_ , LUA_REGISTRYINDEX , " __hype_output" );
399+
400+ // Register plugin API functions (safe even with null db_/img_)
401+ lua_register (L_ , " get_name" , l_get_name);
402+ lua_register (L_ , " set_name" , l_set_name);
403+ lua_register (L_ , " get_func" , l_get_func);
404+ lua_register (L_ , " get_insn" , l_get_insn);
405+ lua_register (L_ , " get_bytes" , l_get_bytes);
406+ lua_register (L_ , " set_comment" , l_set_comment);
407+ lua_register (L_ , " get_xrefs_to" , l_get_xrefs_to);
408+ lua_register (L_ , " get_functions" , l_get_functions);
409+ lua_register (L_ , " print" , l_print);
410+ lua_register (L_ , " goto_addr" , l_goto);
411+ lua_register (L_ , " register_plugin" , l_register_plugin);
412+ lua_register (L_ , " register_menu_item" , l_register_menu_item);
413+ lua_register (L_ , " register_hotkey" , l_register_hotkey);
414+ lua_register (L_ , " register_on_analysis_complete" , l_register_on_analysis_complete);
415+
416+ if (!std::filesystem::exists (dir) || !std::filesystem::is_directory (dir)) {
417+ spdlog::info (" lua_engine: plugins dir not found at {}" , dir.string ());
418+ return ;
419+ }
420+
421+ // Collect and sort .lua files alphabetically
422+ std::vector<std::filesystem::path> files;
423+ for (auto & entry : std::filesystem::directory_iterator (dir)) {
424+ if (entry.path ().extension () == " .lua" )
425+ files.push_back (entry.path ());
426+ }
427+ std::sort (files.begin (), files.end ());
428+
429+ for (auto & fp : files) {
430+ PluginEntry pe;
431+ pe.path = fp.string ();
432+ pe.name = fp.stem ().string (); // default name = filename stem
433+ pe.desc = " " ;
434+
435+ current_plugin_ = pe.path ;
436+ plugins_.push_back (std::move (pe));
437+
438+ output_.clear ();
439+ int err = luaL_dofile (L_ , fp.string ().c_str ());
440+ if (err) {
441+ const char * msg = lua_tostring (L_ , -1 );
442+ plugins_.back ().error = true ;
443+ plugins_.back ().error_msg = msg ? msg : " unknown error" ;
444+ lua_pop (L_ , 1 );
445+ spdlog::warn (" lua_engine: plugin error [{}]: {}" , fp.filename ().string (), plugins_.back ().error_msg );
446+ } else {
447+ spdlog::info (" lua_engine: loaded plugin [{}] -> \" {}\" " ,
448+ fp.filename ().string (), plugins_.back ().name );
449+ }
450+ }
451+
452+ spdlog::info (" lua_engine: {} plugin(s) loaded from {}" , plugins_.size (), dir.string ());
453+ }
454+
455+ void LuaEngine::invoke_menu_item (int plugin_idx, int item_idx) {
456+ if (plugin_idx < 0 || plugin_idx >= static_cast <int >(plugins_.size ())) return ;
457+ auto & plugin = plugins_[static_cast <size_t >(plugin_idx)];
458+ if (item_idx < 0 || item_idx >= static_cast <int >(plugin.items .size ())) return ;
459+ int ref = plugin.items [static_cast <size_t >(item_idx)].cb_ref ;
460+ if (ref == LUA_NOREF ) return ;
461+
462+ output_.clear ();
463+ lua_rawgeti (L_ , LUA_REGISTRYINDEX , ref);
464+ int err = lua_pcall (L_ , 0 , 0 , 0 );
465+ if (err) {
466+ const char * msg = lua_tostring (L_ , -1 );
467+ output_ += std::string (" [plugin error] " ) + (msg ? msg : " unknown" ) + " \n " ;
468+ lua_pop (L_ , 1 );
469+ }
470+ // output_ is forwarded to the Script Console by scriptc_ which reads it via execute()
471+ // For plugin callbacks we flush directly via the output panel pointer stored in registry
472+ lua_getfield (L_ , LUA_REGISTRYINDEX , " __hype_output" );
473+ auto * out_ptr = static_cast <std::string*>(lua_touserdata (L_ , -1 ));
474+ lua_pop (L_ , 1 );
475+ if (out_ptr && out_ptr != &output_) {
476+ *out_ptr += output_;
477+ }
478+ }
479+
480+ void LuaEngine::run_analysis_complete_callbacks () {
481+ for (int ref : on_analysis_cbs_) {
482+ if (ref == LUA_NOREF ) continue ;
483+ output_.clear ();
484+ lua_rawgeti (L_ , LUA_REGISTRYINDEX , ref);
485+ int err = lua_pcall (L_ , 0 , 0 , 0 );
486+ if (err) {
487+ const char * msg = lua_tostring (L_ , -1 );
488+ spdlog::warn (" lua_engine: on_analysis_complete callback error: {}" ,
489+ msg ? msg : " unknown" );
490+ lua_pop (L_ , 1 );
491+ }
492+ }
493+ }
494+
495+ void LuaEngine::check_hotkeys () {
496+ auto & io = ImGui::GetIO ();
497+ if (io.WantTextInput ) return ;
498+ for (auto & [key_str, ref] : hotkeys_) {
499+ if (ref == LUA_NOREF ) continue ;
500+ auto spec = parse_hotkey (key_str);
501+ if (!check_hotkey (spec)) continue ;
502+
503+ output_.clear ();
504+ lua_rawgeti (L_ , LUA_REGISTRYINDEX , ref);
505+ int err = lua_pcall (L_ , 0 , 0 , 0 );
506+ if (err) {
507+ const char * msg = lua_tostring (L_ , -1 );
508+ spdlog::warn (" lua_engine: hotkey callback error: {}" , msg ? msg : " unknown" );
509+ lua_pop (L_ , 1 );
510+ }
511+ }
220512}
221513
222514std::string LuaEngine::execute (const std::string& code) {
@@ -231,4 +523,4 @@ std::string LuaEngine::execute(const std::string& code) {
231523 return output_;
232524}
233525
234- }
526+ } // namespace hype
0 commit comments