Skip to content

Commit ae5b9d3

Browse files
authored
Improve ImGui hooked methods in Object Explorer (#1667)
* List all arguments when "Hook all method" * Add unhook all methods * Add auto unhook when exceed call count
1 parent e778f9d commit ae5b9d3

2 files changed

Lines changed: 101 additions & 1 deletion

File tree

src/mods/tools/ObjectExplorer.cpp

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,24 @@ genny::Enum* enum_from_name(genny::Namespace* g, const std::string& enum_name) {
317317
return new_ns->enum_(namespaces.back());
318318
}
319319

320+
std::string get_method_prototype_context_name(sdk::REMethodDefinition& m) {
321+
auto method_name = m.get_name();
322+
auto method_param_types = m.get_param_types();
323+
324+
std::stringstream ss_context{};
325+
ss_context << method_name << "(";
326+
327+
for (auto i = 0; i < method_param_types.size(); i++) {
328+
if (i > 0) {
329+
ss_context << ", ";
330+
}
331+
ss_context << method_param_types[i]->get_full_name();
332+
}
333+
334+
ss_context << ")";
335+
return ss_context.str();
336+
}
337+
320338
std::shared_ptr<ObjectExplorer>& ObjectExplorer::get() {
321339
static auto instance = std::make_shared<ObjectExplorer>();
322340

@@ -863,6 +881,30 @@ void ObjectExplorer::display_hooks() {
863881
ImGui::EndCombo();
864882
}
865883

884+
// Auto-unhook options
885+
ImGui::Checkbox("Enable Auto Unhook", &m_hooks_context.enable_auto_unhook);
886+
887+
if (m_hooks_context.enable_auto_unhook) {
888+
if (ImGui::BeginCombo("Auto Unhook When", HooksContext::s_auto_unhook_method_names[(uint8_t)m_hooks_context.auto_unhook_method])) {
889+
for (int i = 0; i < HooksContext::s_auto_unhook_method_names.size(); i++) {
890+
const bool is_selected = (m_hooks_context.auto_unhook_method == (HooksContext::AutoUnhookMethod)i);
891+
892+
if (ImGui::Selectable(HooksContext::s_auto_unhook_method_names[i], is_selected)) {
893+
m_hooks_context.auto_unhook_method = (HooksContext::AutoUnhookMethod)i;
894+
}
895+
896+
if (is_selected) {
897+
ImGui::SetItemDefaultFocus();
898+
}
899+
}
900+
901+
ImGui::EndCombo();
902+
}
903+
904+
// Always make it editable regardless of the auto unhook method, this is intentional
905+
ImGui::InputScalar("Max Call Count", ImGuiDataType_U32, &m_hooks_context.auto_unhook_call_count_threshold);
906+
}
907+
866908
ImGui::TreePop();
867909
}
868910

@@ -4269,7 +4311,7 @@ void ObjectExplorer::hook_method(sdk::REMethodDefinition* method, std::optional<
42694311
if (name) {
42704312
hooked.name = method->get_declaring_type()->get_full_name() + "." + *name;
42714313
} else {
4272-
hooked.name = method->get_declaring_type()->get_full_name() + "." + method->get_name();
4314+
hooked.name = method->get_declaring_type()->get_full_name() + "." + get_method_prototype_context_name(*method);
42734315
}
42744316

42754317
using namespace asmjit;
@@ -4342,6 +4384,23 @@ void ObjectExplorer::hook_all_methods(sdk::RETypeDefinition* t) {
43424384
}
43434385
}
43444386

4387+
void ObjectExplorer::unhook_all_methods(sdk::RETypeDefinition* t) {
4388+
if (t == nullptr) {
4389+
return;
4390+
}
4391+
4392+
const auto methods = t->get_methods();
4393+
4394+
for (auto& m : methods) {
4395+
auto it = std::find_if(m_hooked_methods.begin(), m_hooked_methods.end(), [&m](auto& hook) { return hook.method == &m; });
4396+
4397+
if (it != m_hooked_methods.end()) {
4398+
g_hookman.remove(it->method, it->hook_id);
4399+
m_hooked_methods.erase(it);
4400+
}
4401+
}
4402+
}
4403+
43454404
void ObjectExplorer::method_context_menu(sdk::REMethodDefinition* method, std::optional<std::string> name, ::REManagedObject* obj) {
43464405
auto additional_ctx = [&]() {
43474406
auto it = std::find_if(m_hooked_methods.begin(), m_hooked_methods.end(), [method](auto& hook) { return hook.method == method; });
@@ -4377,6 +4436,18 @@ void ObjectExplorer::method_context_menu(sdk::REMethodDefinition* method, std::o
43774436
}
43784437
}
43794438

4439+
if (ImGui::Selectable("Unhook All Methods")) {
4440+
const auto declaring_type = method->get_declaring_type();
4441+
4442+
if (declaring_type != nullptr) {
4443+
std::scoped_lock _{m_job_mutex};
4444+
4445+
m_frame_jobs.push_back([this, declaring_type]() {
4446+
unhook_all_methods(declaring_type);
4447+
});
4448+
}
4449+
}
4450+
43804451
// Allow us to call really simple methods with no params
43814452
if (method->get_param_types().size() == 0) {
43824453
if (obj != nullptr || method->is_static()) {
@@ -5385,6 +5456,20 @@ void ObjectExplorer::post_hooked_method_internal(uintptr_t& ret_val, sdk::REType
53855456
const auto delta = now - hooked_method.stats.last_call_times[tid];
53865457
hooked_method.stats.last_call_delta = delta;
53875458
hooked_method.stats.total_call_time += delta;
5459+
5460+
// Auto-unhook check
5461+
if (m_hooks_context.enable_auto_unhook && m_hooks_context.auto_unhook_method == HooksContext::AutoUnhookMethod::EXCEED_CALL_COUNT) {
5462+
if (hooked_method.stats.call_count >= m_hooks_context.auto_unhook_call_count_threshold) {
5463+
std::scoped_lock job_lock{m_job_mutex};
5464+
m_frame_jobs.push_back([this, method]() {
5465+
auto it2 = std::find_if(m_hooked_methods.begin(), m_hooked_methods.end(), [method](auto& a) { return a.method == method; });
5466+
if (it2 != m_hooked_methods.end()) {
5467+
g_hookman.remove(it2->method, it2->hook_id);
5468+
m_hooked_methods.erase(it2);
5469+
}
5470+
});
5471+
}
5472+
}
53885473
}
53895474

53905475
void ObjectExplorer::post_hooked_method(uintptr_t& ret_val, sdk::RETypeDefinition*& ret_ty, uintptr_t ret_addr, sdk::REMethodDefinition* method) {

src/mods/tools/ObjectExplorer.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ class ObjectExplorer : public Tool {
172172
void context_menu(void* address, std::optional<std::string> name = std::nullopt, std::optional<std::function<void()>> additional_context = std::nullopt);
173173
void hook_method(sdk::REMethodDefinition* method, std::optional<std::string> name);
174174
void hook_all_methods(sdk::RETypeDefinition* type);
175+
void unhook_all_methods(sdk::RETypeDefinition* type);
175176
void method_context_menu(sdk::REMethodDefinition* method, std::optional<std::string> name, ::REManagedObject* obj = nullptr);
176177
void make_same_line_text(std::string_view text, const ImVec4& color);
177178

@@ -255,9 +256,23 @@ class ObjectExplorer : public Tool {
255256
"Number of Threads Called From"
256257
};
257258

259+
enum class AutoUnhookMethod : uint8_t {
260+
NONE,
261+
EXCEED_CALL_COUNT
262+
};
263+
264+
static inline constexpr std::array<const char*, 2> s_auto_unhook_method_names {
265+
"None",
266+
"Exceed Call Count"
267+
};
268+
258269
std::recursive_mutex mtx{};
259270
bool hide_uncalled_methods{false};
260271
SortMethod sort_method{SortMethod::NONE};
272+
273+
bool enable_auto_unhook{false};
274+
AutoUnhookMethod auto_unhook_method{AutoUnhookMethod::NONE};
275+
uint32_t auto_unhook_call_count_threshold{10000};
261276
} m_hooks_context{};
262277

263278
std::recursive_mutex m_job_mutex{};

0 commit comments

Comments
 (0)