Skip to content
Merged
Show file tree
Hide file tree
Changes from 104 commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
efdcce7
start scaffolding EventViewerMod
wildcherry2 Jan 6, 2026
baf336b
middleware prototype for EventViewerMod (concurrency mechanisms)
wildcherry2 Jan 7, 2026
9c34f9d
add cached c-string versions of string in CallStackEntry for ImGui
wildcherry2 Jan 8, 2026
09f7b83
renamed Renderer to Client and began rendering imgui components
wildcherry2 Jan 9, 2026
cd7df1f
add prototype save state/load state functionality
wildcherry2 Jan 10, 2026
05896ab
Working prototype for EventViewerMod
wildcherry2 Jan 12, 2026
01e9fd9
Fix issue where middleware's hook target wasn't being synced correctl…
wildcherry2 Jan 12, 2026
9aac9a8
Use a string pool with string views to reduce memory usage
wildcherry2 Jan 12, 2026
e52e27a
Add profiler for queue operations
wildcherry2 Jan 12, 2026
3ea3ec2
Refactor StringPool to use half the number of strings it did previously
wildcherry2 Jan 12, 2026
65ac523
Take out thread scheme switcher and only use ConcurrentQueue, which a…
wildcherry2 Jan 12, 2026
92612d1
Add back Middleware dtor and remove std::call_once since it's now a s…
wildcherry2 Jan 12, 2026
dd0abd0
Support lowercase comparisons when filtering strings
wildcherry2 Jan 13, 2026
4afca3e
Middleware singleton doesnt use a pointer anymore
wildcherry2 Jan 13, 2026
ab4a86d
Add ProcessLocalScriptFunction support
wildcherry2 Jan 13, 2026
5252aa1
Attempt to fix hidden scroll bar with manual style calculation
wildcherry2 Jan 13, 2026
9689f8f
Refactor client and supporting structs to centralize all calls on a p…
wildcherry2 Jan 14, 2026
ac5ac62
Add right-click context menu and tooltips over entries when the strea…
wildcherry2 Jan 14, 2026
588c4a5
Add caller as unformatted text that exists on the same line as the entry
wildcherry2 Jan 14, 2026
5548187
Add support menus to CallFrequencyEntry
wildcherry2 Jan 14, 2026
20312c8
Simplify support menu rendering by add push/pop id in render_view
wildcherry2 Jan 15, 2026
cc709a8
Merge branch 'UE4SS-RE:main' into main
wildcherry2 Jan 15, 2026
81ccd69
Add to_string log helper to CallStackEntry
wildcherry2 Jan 15, 2026
d8e98a0
Add prototype modal for viewing an entry's call stack.
wildcherry2 Jan 16, 2026
b80ed91
Fix some sizing/modal issues
wildcherry2 Jan 16, 2026
6511598
Saving an individual call stack for an entry now works.
wildcherry2 Jan 16, 2026
516fce8
Refactor entry rendering to use flags and a single function, and add …
wildcherry2 Jan 16, 2026
1479db7
Fix issue with flags and support menus
wildcherry2 Jan 16, 2026
933cb84
Default to 'All' target.
wildcherry2 Jan 16, 2026
df363ab
Fix default thread not going to game thread when not explicitly chosen
wildcherry2 Jan 16, 2026
e223db0
Add string pool for function paths and rearrange things in get_string…
wildcherry2 Jan 16, 2026
2b5b2e1
Clicking 'Clear All' clears the thread list as well now.
wildcherry2 Jan 16, 2026
b133cb3
Add metric for 'Time Slot Exceeded'
wildcherry2 Jan 16, 2026
0a9efd7
Remove unnecessary anonymous namespace in Client.cpp
wildcherry2 Jan 17, 2026
70accd5
add clear function to StringPool
wildcherry2 Jan 17, 2026
01faaaf
Drastically improve performance with text virtualization
wildcherry2 Jan 17, 2026
a88c10c
Use frame style in view for better contrast
wildcherry2 Jan 17, 2026
0e122e6
Add modal when saving that shows the path.
wildcherry2 Jan 17, 2026
65830e0
Fix frequency filtering bug
wildcherry2 Jan 17, 2026
ff4b5cc
Add full name hash for future filtering optimizations
wildcherry2 Jan 17, 2026
8f14e2b
Add warning for adding Caller + Function name to filters.
wildcherry2 Jan 17, 2026
e47fa54
Address remaining todos
wildcherry2 Jan 17, 2026
24d283e
Rename .h files to .hpp, fix CMakeLists.txt.
wildcherry2 Jan 17, 2026
6cfc6d1
Add some documentation
wildcherry2 Jan 18, 2026
b6fa293
Add FName.toString check in Middleware constructor
wildcherry2 Jan 18, 2026
5a6760e
Move FName.toString check from Middleware constructor to start() in c…
wildcherry2 Jan 18, 2026
f4c05ba
Do manual layout in EntryCallStackRenderer to make the view area more…
wildcherry2 Jan 18, 2026
a31bdc9
Add optional filter count between filtered calls to indicate that nei…
wildcherry2 Jan 18, 2026
8a56acc
Update most built in hooks to use the new hook system.
wildcherry2 Jan 18, 2026
864e545
Merge pull request #1 from wildcherry2/hook-system-integration
wildcherry2 Jan 18, 2026
4b0e320
Use a 'render set' that perpetually keeps track of which entries to r…
wildcherry2 Jan 20, 2026
1ef7321
start scaffolding EventViewerMod
wildcherry2 Jan 6, 2026
dc14b5a
middleware prototype for EventViewerMod (concurrency mechanisms)
wildcherry2 Jan 7, 2026
3268767
add cached c-string versions of string in CallStackEntry for ImGui
wildcherry2 Jan 8, 2026
506b25c
renamed Renderer to Client and began rendering imgui components
wildcherry2 Jan 9, 2026
317c50d
add prototype save state/load state functionality
wildcherry2 Jan 10, 2026
3c55d30
Working prototype for EventViewerMod
wildcherry2 Jan 12, 2026
3a6b54c
Fix issue where middleware's hook target wasn't being synced correctl…
wildcherry2 Jan 12, 2026
c28425a
Use a string pool with string views to reduce memory usage
wildcherry2 Jan 12, 2026
6ac58b2
Add profiler for queue operations
wildcherry2 Jan 12, 2026
0fed97c
Refactor StringPool to use half the number of strings it did previously
wildcherry2 Jan 12, 2026
fb76101
Take out thread scheme switcher and only use ConcurrentQueue, which a…
wildcherry2 Jan 12, 2026
2e5aace
Add back Middleware dtor and remove std::call_once since it's now a s…
wildcherry2 Jan 12, 2026
4be2d08
Support lowercase comparisons when filtering strings
wildcherry2 Jan 13, 2026
5e44174
Middleware singleton doesnt use a pointer anymore
wildcherry2 Jan 13, 2026
e0b1c53
Add ProcessLocalScriptFunction support
wildcherry2 Jan 13, 2026
0f449e0
Attempt to fix hidden scroll bar with manual style calculation
wildcherry2 Jan 13, 2026
1693eb7
Refactor client and supporting structs to centralize all calls on a p…
wildcherry2 Jan 14, 2026
44b590a
Add right-click context menu and tooltips over entries when the strea…
wildcherry2 Jan 14, 2026
1bbc6cc
Add caller as unformatted text that exists on the same line as the entry
wildcherry2 Jan 14, 2026
07d2ae1
Add support menus to CallFrequencyEntry
wildcherry2 Jan 14, 2026
b3098fc
Simplify support menu rendering by add push/pop id in render_view
wildcherry2 Jan 15, 2026
ab44656
Add to_string log helper to CallStackEntry
wildcherry2 Jan 15, 2026
d3b4c65
Add prototype modal for viewing an entry's call stack.
wildcherry2 Jan 16, 2026
c39ca2b
Fix some sizing/modal issues
wildcherry2 Jan 16, 2026
4bbb8f4
Saving an individual call stack for an entry now works.
wildcherry2 Jan 16, 2026
78e8b6a
Refactor entry rendering to use flags and a single function, and add …
wildcherry2 Jan 16, 2026
d5bad86
Fix issue with flags and support menus
wildcherry2 Jan 16, 2026
d7b2f11
Default to 'All' target.
wildcherry2 Jan 16, 2026
9a22838
Fix default thread not going to game thread when not explicitly chosen
wildcherry2 Jan 16, 2026
83ac654
Add string pool for function paths and rearrange things in get_string…
wildcherry2 Jan 16, 2026
9eab934
Clicking 'Clear All' clears the thread list as well now.
wildcherry2 Jan 16, 2026
f284df4
Add metric for 'Time Slot Exceeded'
wildcherry2 Jan 16, 2026
402f2cb
Remove unnecessary anonymous namespace in Client.cpp
wildcherry2 Jan 17, 2026
87fae06
add clear function to StringPool
wildcherry2 Jan 17, 2026
e9f3a97
Drastically improve performance with text virtualization
wildcherry2 Jan 17, 2026
860749c
Use frame style in view for better contrast
wildcherry2 Jan 17, 2026
ea1ae5e
Add modal when saving that shows the path.
wildcherry2 Jan 17, 2026
82cd5e1
Fix frequency filtering bug
wildcherry2 Jan 17, 2026
b0b4e65
Add full name hash for future filtering optimizations
wildcherry2 Jan 17, 2026
c1319e9
Add warning for adding Caller + Function name to filters.
wildcherry2 Jan 17, 2026
2d05c7d
Address remaining todos
wildcherry2 Jan 17, 2026
8235fe1
Rename .h files to .hpp, fix CMakeLists.txt.
wildcherry2 Jan 17, 2026
3738b69
Add some documentation
wildcherry2 Jan 18, 2026
c9427b8
Add FName.toString check in Middleware constructor
wildcherry2 Jan 18, 2026
f329436
Move FName.toString check from Middleware constructor to start() in c…
wildcherry2 Jan 18, 2026
b857fd7
Do manual layout in EntryCallStackRenderer to make the view area more…
wildcherry2 Jan 18, 2026
fc71fee
Add optional filter count between filtered calls to indicate that nei…
wildcherry2 Jan 18, 2026
3934951
Update most built in hooks to use the new hook system.
wildcherry2 Jan 18, 2026
bb5ce3b
Use a 'render set' that perpetually keeps track of which entries to r…
wildcherry2 Jan 20, 2026
1c92878
Merge remote-tracking branch 'origin/main'
wildcherry2 Jan 24, 2026
90ea31d
Update release.py with EventViewerMod
wildcherry2 Jan 24, 2026
7166f4b
chore: Ran clang-format
UE4SS Jan 26, 2026
288ef65
chore: clang-format exceptions
UE4SS Jan 26, 2026
27547ab
Use register_tab in mod constructor with ImGui::Begin/EndDisabled unt…
wildcherry2 Jan 26, 2026
ae91c7c
Add and use to_lower_case helpers in Helpers/String.hpp.
wildcherry2 Jan 26, 2026
42a1ea3
Run clang-format.
wildcherry2 Jan 26, 2026
129ef4b
Rerun clang-format.
wildcherry2 Jan 26, 2026
53c9078
chore: Added last remaining Lua hook (LoadMap) to the new system
UE4SS Jan 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion UE4SS/include/UE4SSProgram.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,6 @@ namespace RC
friend void* HookedLoadLibraryExA(const char* dll_name, void* file, int32_t flags);
friend void* HookedLoadLibraryW(const wchar_t* dll_name);
friend void* HookedLoadLibraryExW(const wchar_t* dll_name, void* file, int32_t flags);
friend auto gui_render_thread_tick(Unreal::UObject*, float) -> void;
friend auto gui_render_thread_tick() -> void;
};
} // namespace RC
4 changes: 2 additions & 2 deletions UE4SS/src/GUI/UFunctionCallerWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ namespace RC::GUI
static FOutputDevice s_ar{};
static UFunction* s_function{};
static UObject* s_executor{};
auto call_process_console_exec(UObject*, UFunction*, void*) -> void
auto call_process_console_exec(Hook::TCallbackIterationData<void>&, UObject*, UFunction*, void*) -> void
{
if (s_do_call)
{
Expand Down Expand Up @@ -173,7 +173,7 @@ namespace RC::GUI
if (!s_is_hooked)
{
s_is_hooked = true;
Hook::RegisterProcessEventPostCallback(call_process_console_exec);
Hook::RegisterProcessEventPostCallback(call_process_console_exec, {false, false, STR("UE4SS"), STR("FunctionCallerWidgetHook")});
}
s_do_call = true;
}
Expand Down
44 changes: 24 additions & 20 deletions UE4SS/src/Mod/LuaMod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3773,7 +3773,8 @@ No overload found for function 'IsInGameThread'.
});
}

auto static process_event_hook([[maybe_unused]] Unreal::UObject* Context,
auto static process_event_hook([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData,
[[maybe_unused]] Unreal::UObject* Context,
[[maybe_unused]] Unreal::UFunction* Function,
[[maybe_unused]] void* Parms) -> void
{
Expand All @@ -3783,7 +3784,10 @@ No overload found for function 'IsInGameThread'.
process_delayed_actions<GameThreadExecutionMethod::ProcessEvent>(LuaMod::m_delayed_game_thread_actions);
}

auto static engine_tick_hook([[maybe_unused]] Unreal::UEngine* Context, [[maybe_unused]] float DeltaSeconds) -> void
auto static engine_tick_hook([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData,
[[maybe_unused]] Unreal::UEngine* Context,
[[maybe_unused]] float DeltaSeconds,
[[maybe_unused]] bool bIdle) -> void
{
std::lock_guard<std::recursive_mutex> guard{LuaMod::m_thread_actions_mutex};

Expand All @@ -3807,7 +3811,7 @@ No overload found for function 'IsInGameThread'.
{
if (!m_is_engine_tick_hooked)
{
Unreal::Hook::RegisterEngineTickPreCallback(&engine_tick_hook);
Unreal::Hook::RegisterEngineTickPreCallback(engine_tick_hook, {false, false, STR("UE4SS"), STR("LuaModImpl")});
m_is_engine_tick_hooked = true;
}
}
Expand All @@ -3817,7 +3821,7 @@ No overload found for function 'IsInGameThread'.
{
if (!mod->m_is_process_event_hooked)
{
Unreal::Hook::RegisterProcessEventPreCallback(&process_event_hook);
Unreal::Hook::RegisterProcessEventPreCallback(process_event_hook, {false, false, STR("UE4SS"), STR("LuaModImpl")});
mod->m_is_process_event_hooked = true;
}
}
Expand Down Expand Up @@ -5562,7 +5566,7 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
LuaStatics::console_executor_enabled = false;
}

static auto script_hook([[maybe_unused]] Unreal::UObject* Context, Unreal::FFrame& Stack, [[maybe_unused]] void* RESULT_DECL) -> void
static auto script_hook([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData, [[maybe_unused]] Unreal::UObject* Context, Unreal::FFrame& Stack, [[maybe_unused]] void* RESULT_DECL) -> void
{
std::lock_guard<std::recursive_mutex> guard{LuaMod::m_thread_actions_mutex};

Expand Down Expand Up @@ -5699,7 +5703,7 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
auto LuaMod::on_program_start() -> void
{
Unreal::UObjectArray::AddUObjectDeleteListener(&LuaType::FLuaObjectDeleteListener::s_lua_object_delete_listener);

const Unreal::Hook::FCallbackOptions common_opts {false, false, STR("UE4SS"), STR("LuaModImpl")};
Unreal::Hook::RegisterLoadMapPreCallback(
[](Unreal::UEngine* Engine, Unreal::FWorldContext& WorldContext, Unreal::FURL URL, Unreal::UPendingNetGame* PendingGame, Unreal::FString& Error)
-> std::pair<bool, bool> {
Expand Down Expand Up @@ -5780,7 +5784,7 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
});
});

Unreal::Hook::RegisterInitGameStatePreCallback([]([[maybe_unused]] Unreal::AGameModeBase* Context) {
Unreal::Hook::RegisterInitGameStatePreCallback([]([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData, [[maybe_unused]] Unreal::AGameModeBase* Context) {
TRY([&] {
for (const auto& callback_data : m_init_game_state_pre_callbacks)
{
Expand All @@ -5795,9 +5799,9 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
}
}
});
});
}, common_opts);

Unreal::Hook::RegisterInitGameStatePostCallback([]([[maybe_unused]] Unreal::AGameModeBase* Context) {
Unreal::Hook::RegisterInitGameStatePostCallback([]([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData, [[maybe_unused]] Unreal::AGameModeBase* Context) {
TRY([&] {
for (const auto& callback_data : m_init_game_state_post_callbacks)
{
Expand All @@ -5812,9 +5816,9 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
}
}
});
});
}, common_opts);

Unreal::Hook::RegisterBeginPlayPreCallback([]([[maybe_unused]] Unreal::AActor* Context) {
Unreal::Hook::RegisterBeginPlayPreCallback([]([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData, [[maybe_unused]] Unreal::AActor* Context) {
TRY([&] {
for (const auto& callback_data : m_begin_play_pre_callbacks)
{
Expand All @@ -5829,9 +5833,9 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
}
}
});
});
}, common_opts);

Unreal::Hook::RegisterBeginPlayPostCallback([]([[maybe_unused]] Unreal::AActor* Context) {
Unreal::Hook::RegisterBeginPlayPostCallback([]([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData, [[maybe_unused]] Unreal::AActor* Context) {
TRY([&] {
for (const auto& callback_data : m_begin_play_post_callbacks)
{
Expand All @@ -5846,9 +5850,9 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
}
}
});
});
}, common_opts);

Unreal::Hook::RegisterEndPlayPreCallback([]([[maybe_unused]] Unreal::AActor* Context, Unreal::EEndPlayReason EndPlayReason) {
Unreal::Hook::RegisterEndPlayPreCallback([]([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData, [[maybe_unused]] Unreal::AActor* Context, Unreal::EEndPlayReason EndPlayReason) {
TRY([&] {
for (const auto& callback_data : m_end_play_pre_callbacks)
{
Expand All @@ -5865,9 +5869,9 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
}
}
});
});
}, common_opts);

Unreal::Hook::RegisterEndPlayPostCallback([]([[maybe_unused]] Unreal::AActor* Context, Unreal::EEndPlayReason EndPlayReason) {
Unreal::Hook::RegisterEndPlayPostCallback([]([[maybe_unused]] Unreal::Hook::TCallbackIterationData<void>& CallbackIterationData, [[maybe_unused]] Unreal::AActor* Context, Unreal::EEndPlayReason EndPlayReason) {
TRY([&] {
for (const auto& callback_data : m_end_play_post_callbacks)
{
Expand All @@ -5884,7 +5888,7 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
}
}
});
});
}, common_opts);

Unreal::Hook::RegisterStaticConstructObjectPostCallback([](const Unreal::FStaticConstructObjectParameters&, Unreal::UObject* constructed_object) {
return TRY([&] {
Expand Down Expand Up @@ -6412,12 +6416,12 @@ No overload found for function 'FPackageName:IsValidLongPackageName'.
if (Unreal::UObject::ProcessLocalScriptFunctionInternal.is_ready() && Unreal::Version::IsAtLeast(4, 22))
{
Output::send(STR("Enabling custom events\n"));
Unreal::Hook::RegisterProcessLocalScriptFunctionPostCallback(script_hook);
Unreal::Hook::RegisterProcessLocalScriptFunctionPostCallback(script_hook, {false, false, STR("UE4SS"), STR("LuaModImplScriptHook")});
}
else if (Unreal::UObject::ProcessInternalInternal.is_ready() && Unreal::Version::IsBelow(4, 22))
{
Output::send(STR("Enabling custom events\n"));
Unreal::Hook::RegisterProcessInternalPostCallback(script_hook);
Unreal::Hook::RegisterProcessInternalPostCallback(script_hook, {false, false, STR("UE4SS"), STR("LuaModImplScriptHook")});
}
}

Expand Down
6 changes: 3 additions & 3 deletions UE4SS/src/UE4SSProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ namespace RC

static bool s_gui_initialized_for_game_thread{};
static bool s_gui_initializing_for_game_thread{};
auto gui_render_thread_tick(Unreal::UObject*, float) -> void
auto gui_render_thread_tick() -> void
{
if (UE4SSProgram::settings_manager.Debug.RenderMode == GUI::RenderMode::ExternalThread)
{
Expand Down Expand Up @@ -941,11 +941,11 @@ namespace RC

if (settings_manager.Debug.RenderMode == GUI::RenderMode::EngineTick)
{
Hook::RegisterEngineTickPostCallback(gui_render_thread_tick);
Hook::RegisterEngineTickPostCallback([](auto&,...){gui_render_thread_tick(); }, {false, false, STR("UE4SS"), STR("ImGuiRenderHook")});
}
else if (settings_manager.Debug.RenderMode == GUI::RenderMode::GameViewportClientTick)
{
Hook::RegisterGameViewportClientTickPostCallback(gui_render_thread_tick);
Hook::RegisterGameViewportClientTickPostCallback([](auto&,...){gui_render_thread_tick(); }, {false, false, STR("UE4SS"), STR("ImGuiRenderHook")});
}

if (settings_manager.Debug.DebugConsoleEnabled)
Expand Down
1 change: 1 addition & 0 deletions cppmods/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)

# Add C++ mods
add_subdirectory("KismetDebuggerMod")
add_subdirectory("EventViewerMod")

# Organize targets in the "mods" folder
get_property(TARGETS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY BUILDSYSTEM_TARGETS)
Expand Down
28 changes: 28 additions & 0 deletions cppmods/EventViewerMod/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.22)
set(TARGET EventViewerMod)
project(${TARGET})

include(FetchContent)

FetchContent_Declare(
concurrentqueue
GIT_REPOSITORY [email protected]:cameron314/concurrentqueue.git
GIT_TAG c68072129c8a5b4025122ca5a0c82ab14b30cb03
)
FetchContent_MakeAvailable(concurrentqueue)

add_library(${TARGET} SHARED
src/dllmain.cpp
src/EventViewer.cpp
src/Middleware.cpp
src/Client.cpp
src/Structs.cpp
src/StringPool.cpp
src/EntryCallStackRenderer.cpp
src/FilterCountRenderer.cpp
)

target_include_directories(${TARGET} PRIVATE "include")
target_link_libraries(${TARGET} PRIVATE ImGui)
target_link_libraries(${TARGET} PRIVATE concurrentqueue)
target_link_libraries(${TARGET} PUBLIC UE4SS)
128 changes: 128 additions & 0 deletions cppmods/EventViewerMod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# EventViewerMod

A UE4SS C++ mod that captures Unreal Engine call flow and renders it live in ImGui.

It hooks **ProcessEvent**, **ProcessInternal**, and **ProcessLocalScriptFunction** concurrently and uses a **single
unified depth counter** so nested and recursive call chains keep a consistent indentation story (PE → PI → PLSF → …).

## What you can do

- Watch a **live call stack** with depth-indented entries.
- Switch between **Stack** and **Frequency** modes.
- Filter captures with **case-insensitive** whitelist/blacklist substring rules.
- Pause the stream and use right-click context menus to copy names, add filters, or open a focused call-stack modal.
- Save the current view (or everything) to a timestamped text file.

## UI overview

### Enable / Start / Pause

- **Enable** toggles the mod on/off (and persists that setting).
- **Start/Stop** controls whether the middleware is actively capturing and dequeuing.
- **Pause** keeps capturing logic installed but stops dequeuing and UI growth.

### Target filter

The **Target** combo is a *view filter*, not a capture filter:

- **All** shows the call stack exactly as the middleware reports it.
- **ProcessEvent / ProcessInternal / ProcessLocalScriptFunction** show only entries that originated from that hook.

Important: depth is **not** recomputed when you filter. If you hide callers, the remaining entries keep their original
depth so you can still read the true nesting structure.

### Modes

- **Stack**: live call stack history (ordered by time, per thread).
- **Frequency**: aggregates by function and tracks how often it appears.

### Thread picker

Captures are grouped by the originating `std::thread::id`. The combo lets you switch which thread you’re viewing. The
game thread is labeled with `(Game)` when detected.

### Performance knobs

- **Max MS Read Time** and **Max Count Per Iteration** bound how much work `dequeue()` is allowed to do per ImGui frame.

### Saving captures

- **Save** writes the current thread + current mode to a timestamped file.
- **Save All** dumps both modes for all threads.

## Filtering (case-insensitive)

Whitelist and blacklist entries are **comma-separated tokens**.

- Tokens are trimmed and converted to lowercase (ASCII-only lowercasing).
- Filtering is done against the entry’s cached **lower-cased** strings.
- The UI always displays the original (non-lowercased) names.

Rules:

- **Whitelist**: if empty, everything passes. If non-empty, an entry passes if **any** whitelist token is a substring
match.
- **Blacklist**: if any blacklist token is a substring match, the entry fails.
- **Show Tick Functions** is an additional filter gate applied on top.

## Right-click menus

Both stack entries and frequency entries have a right-click menu (when enabled by the current render flags) with helpers
such as:

- Copy function/caller names to clipboard
- Add function/caller to whitelist/blacklist
- Open the call stack modal (when the stream is paused)

## Call stack modal

When the stream is paused, the context menu can open a modal window that shows an entry’s root call chain.

Definitions:

- The **root caller** of an entry is the depth `0` entry that began the call chain that ultimately led to the selected
entry.

The modal provides:

- **Show full context**
- Enabled: shows all calls produced by the root caller (the entire subtree under that root).
- Disabled: shows the path from the root → selected entry, plus the calls triggered by the selected entry.
- **Disable Indent Colors**
- Mirrors the main window’s behavior.

## Architecture (high-level)

- **Middleware** (`include/Middleware.hpp`, `src/Middleware.cpp`)
- Owns the UE hooks and pushes lightweight capture entries into a `moodycamel::ConcurrentQueue`.
- Uses thread-local producer tokens for low overhead under high call volume.
- Uses a one-time barrier/flag to prevent enqueuing until all hooks are installed (to keep depth sane).

- **Client** (`include/Client.hpp`, `src/Client.cpp`)
- ImGui renderer + persistent UI state.
- Dequeues entries, groups by thread, maintains stack/frequency views, and applies filters.

- **StringPool** (`include/StringPool.hpp`, `src/StringPool.cpp`)
- Interns function and caller strings and returns stable `std::string_view` pairs.
- Caches both original and lowercased variants.
- Produces a function hash (from Unreal’s `ComparisonIndex`) to avoid expensive string comparisons in hot paths.

- **EntryCallStackRenderer** (`include/EntryCallStackRenderer.hpp`, `src/EntryCallStackRenderer.cpp`)
- Manages the call-stack modal’s state and rendering.

## Files and persistence

- UI state is stored as JSON at:
- `Mods/EventViewerMod/config/settings.json`
- Capture dumps are written to:
- `Mods/EventViewerMod/captures/`

## Building

This mod is intended to be compiled as a UE4SS C++ mod (MSVC, `/std:c++latest`).

## Notes and gotchas

- String views returned from `StringPool` are stable until the pool is cleared. The current implementation is designed
for “grow-only” usage during a session and never clears, but if later implementation does want to support clearing it,
they should also clear all threads.
Loading