Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
bdf59c4
refactor(render): measure text via a stable font, not the live ImGui …
47PADO47 Sep 6, 2026
e6e9f88
chore(visuals): log external-overlay draw stats to diagnose the blank…
47PADO47 Sep 6, 2026
00a0a96
feat(settings): add MenuBackend (imgui/canvas) menu-engine setting
47PADO47 Sep 6, 2026
f108115
feat(menu): recover ZeroGUI/ZeroInput and add the Menu::UI widget facade
47PADO47 Sep 6, 2026
4acacd6
feat(menu): add the Canvas menu tick and wire both backends into the …
47PADO47 Sep 6, 2026
6391ba2
refactor(menu): drive the sections through Menu::UI, degrade hard tab…
47PADO47 Sep 6, 2026
04a8322
chore: update world assignment
47PADO47 Sep 6, 2026
e2c6fab
refactor(render): rename Palette::Cream to Background, add Success/Muted
47PADO47 Sep 6, 2026
bf760e4
feat(shared): add PasteFromClipboard to Shared::Utilities
47PADO47 Sep 6, 2026
96d511c
feat(input): add immediate-mode GUI helpers to the Input namespace
47PADO47 Sep 6, 2026
5827673
feat(menu): add UCanvasGUI::Colors theme seeded from the palette
47PADO47 Sep 6, 2026
e35848e
refactor(menu): rename ZeroGUI to UCanvasGUI and split it into per-wi…
47PADO47 Sep 6, 2026
79f84a0
feat(menu): add the Menu::Backend interface and Phase enum
47PADO47 Sep 6, 2026
58e2a90
feat(menu): implement ImGuiBackend
47PADO47 Sep 6, 2026
8ef599a
feat(menu): implement CanvasBackend over UCanvasGUI
47PADO47 Sep 6, 2026
b59bf94
feat(menu): add the backend registry (instances, table, Select)
47PADO47 Sep 6, 2026
fecacf5
refactor(menu): rewrite Menu::UI as pure forwarders, add UI::Count
47PADO47 Sep 6, 2026
47a46e3
refactor(menu): drive the menu via Menu::Frame(Phase); drop Draw/Tick
47PADO47 Sep 6, 2026
114a3c7
chore(menu): remove the obsolete Widgets.h
47PADO47 Sep 6, 2026
80abd03
refactor(menu): draw the ImGui menu from Menu::Frame(Present)
47PADO47 Sep 6, 2026
05e6a1b
refactor(hook): drive the canvas menu via Menu::Frame, select rendere…
47PADO47 Sep 6, 2026
bd2813c
docs(settings): rename ZeroGUI to UCanvasGUI in the MenuBackend comment
47PADO47 Sep 6, 2026
cacc3b9
chore(build): register the new menu headers, drop ZeroGUI/ZeroInput
47PADO47 Sep 6, 2026
530111b
chore(menu): use UI::Count instead of ImGui's IM_ARRAYSIZE
47PADO47 Sep 6, 2026
36351e5
refactor(menu): route the Discord tab through the UI facade
47PADO47 Sep 6, 2026
fdf47aa
refactor(menu): route the Scripts tab through the UI facade
47PADO47 Sep 6, 2026
c46a783
refactor(menu): route the Config tab through the UI facade
47PADO47 Sep 6, 2026
6b488f2
refactor(menu): route the Debug tab through the UI facade
47PADO47 Sep 6, 2026
272d0e2
refactor(menu): route the Network tab through the UI facade
47PADO47 Sep 6, 2026
44726c3
refactor(menu): route the SDK tab through the UI facade
47PADO47 Sep 6, 2026
0ffba3d
refactor(menu): route the Misc tab through the UI facade
47PADO47 Sep 6, 2026
726fe3d
fix(network): silence C4244 in WinHttp::Narrow with an explicit cast
47PADO47 Sep 6, 2026
61053bc
feat(events): add EnteredGame/EnteredLobby, dispatched on the IsInGam…
47PADO47 Sep 6, 2026
4e4d16a
feat(features): allow multiple triggers per feature and forward the e…
47PADO47 Sep 6, 2026
2874f87
refactor(features): drive DiscordPresence off EnteredGame/EnteredLobby
47PADO47 Sep 6, 2026
d12b180
docs(features): document multi-trigger features and the Run() overloads
47PADO47 Sep 6, 2026
4aefbdc
feat(features): add a per-feature Run() throttle (ThrottleMs)
47PADO47 Sep 6, 2026
91a6bf4
refactor(features): refresh DiscordPresence K/D on PlayerKilled
47PADO47 Sep 6, 2026
39bb741
feat(native): add a stable ActorId accessor (UObject InternalIndex)
47PADO47 Sep 6, 2026
0aafd13
feat(hook): add the GuardHook guard-page hooking primitive
47PADO47 Sep 6, 2026
7af4db0
chore(build): register native/ActorId.h and hook/GuardHook.h
47PADO47 Sep 6, 2026
9735992
docs(roadmap): unblock trace-redirect silent aim (reflected hit RPCs …
47PADO47 Sep 6, 2026
1043964
revert: drop the ActorId accessor
47PADO47 Sep 6, 2026
ede5f28
feat(settings): add VISUALS.CanvasFont (canvas renderer text font)
47PADO47 Sep 6, 2026
462d07c
feat(cache): add FontCache listing every UFont in GObjects
47PADO47 Sep 6, 2026
0fb6afc
feat(render): draw canvas text in the configured font
47PADO47 Sep 6, 2026
9863605
feat(menu): add a canvas font picker to the Visuals tab
47PADO47 Sep 6, 2026
f21f54c
chore(build): register cache/FontCache.h
47PADO47 Sep 6, 2026
9082aa2
test(features): cover multi-trigger, throttle, and Run() overload dis…
47PADO47 Sep 6, 2026
e1ff488
refactor(menu): dispatch SettingsChanged per-toggle across the tabs
47PADO47 Sep 6, 2026
ca7bbe3
feat(hook): add GuardHook::SelfTest and a Debug-tab button to run it
47PADO47 Sep 6, 2026
9afdc4d
feat(menu): add a Dump FName pool button to the SDK tab
47PADO47 Sep 6, 2026
2d2ae31
Update Hook.h
47PADO47 Sep 7, 2026
67ad90c
ci: make MSBuild workflow dispatch-only and bump actions to latest
47PADO47 Sep 14, 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
18 changes: 4 additions & 14 deletions .github/workflows/msbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@ name: MSBuild

on:
workflow_dispatch:
push:
branches: [ "master" ]
paths:
- 'Internal/**'
- 'Launcher/**'
pull_request:
branches: [ "master" ]
paths:
- 'Internal/**'
- 'Launcher/**'

env:
SOLUTION_FILE_PATH: .
Expand All @@ -24,16 +14,16 @@ jobs:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
id: cpy
with:
python-version: '3.14'

- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v2
uses: microsoft/setup-msbuild@v3

- name: Install vcpkg
run: |
Expand All @@ -60,7 +50,7 @@ jobs:
run: Tools/build.bat

- name: Upload Release Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: Release
path: ${{env.SOLUTION_FILE_PATH}}/x64/Release
Expand Down
24 changes: 24 additions & 0 deletions Internal/Internal.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
<ItemGroup>
<ClInclude Include="cache\ActorCache.h" />
<ClInclude Include="cache\ClassCache.h" />
<ClInclude Include="cache\FontCache.h" />
<ClInclude Include="cache\NameCache.h" />
<ClInclude Include="render\Vec2.h" />
<ClInclude Include="render\Color.h" />
Expand Down Expand Up @@ -242,6 +243,7 @@
<ClInclude Include="ue\sdk\UWorld.h" />
<ClInclude Include="ue\sdk\Values.h" />
<ClInclude Include="hook\Hook.h" />
<ClInclude Include="hook\GuardHook.h" />
<ClInclude Include="features\Features.h" />
<ClInclude Include="features\FeatureRunner.h" />
<ClInclude Include="discord\handlers.h" />
Expand All @@ -264,6 +266,28 @@
<ClInclude Include="memory\Memory.h" />
<ClInclude Include="utils\Rgb.h" />
<ClInclude Include="menu\Menu.h" />
<ClInclude Include="menu\canvas\Colors.h" />
<ClInclude Include="menu\canvas\UCanvasGUI.h" />
<ClInclude Include="menu\canvas\components\Core.h" />
<ClInclude Include="menu\canvas\components\Window.h" />
<ClInclude Include="menu\canvas\components\Text.h" />
<ClInclude Include="menu\canvas\components\Tab.h" />
<ClInclude Include="menu\canvas\components\Button.h" />
<ClInclude Include="menu\canvas\components\Checkbox.h" />
<ClInclude Include="menu\canvas\components\Slider.h" />
<ClInclude Include="menu\canvas\components\Combobox.h" />
<ClInclude Include="menu\canvas\components\Hotkey.h" />
<ClInclude Include="menu\canvas\components\ColorPicker.h" />
<ClInclude Include="menu\canvas\components\TextField.h" />
<ClInclude Include="menu\canvas\components\Selectable.h" />
<ClInclude Include="menu\canvas\components\CollapsingHeader.h" />
<ClInclude Include="menu\canvas\components\Combo.h" />
<ClInclude Include="menu\canvas\components\Child.h" />
<ClInclude Include="menu\ui\UI.h" />
<ClInclude Include="menu\backend\Backend.h" />
<ClInclude Include="menu\backend\ImGuiBackend.h" />
<ClInclude Include="menu\backend\CanvasBackend.h" />
<ClInclude Include="menu\backend\Backends.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
Expand Down
60 changes: 60 additions & 0 deletions Internal/cache/FontCache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#pragma once

/// @file
/// @brief Lazily-built, shared list of every UFont in GObjects (short name + pointer), so the UE-canvas
/// renderer can draw text in a chosen game font instead of the default Roboto. Mirrors ClassCache: fonts
/// load once and rarely change, so the walk is done on demand and reused; Rebuild() (a Refresh button)
/// picks up newly-loaded fonts.

#include <string>
#include <vector>

#include "../ue/Engine.h"

namespace FontCache
{
/// One cached font: its short name (for display / the setting) and the object pointer.
struct Entry
{
std::string name;
UFont* font;
};

inline std::vector<Entry> fonts;
inline bool built = false;

/// Walk GObjects once and collect every UFont instance (skipping the class default object).
inline void Rebuild()
{
fonts.clear();
built = true;
if (!Engine::GObjects) return;

UObject* fontClass = Engine::GObjects->FindObject("Class Engine.Font");
if (!fontClass) return;

const auto count = Engine::GObjects->NumElements;
for (auto i = 0u; i < count; i++)
{
auto* obj = Engine::GObjects->GetObjectPtr(i);
if (!obj || obj->IsDefaultObject() || !obj->IsA(fontClass)) continue;
fonts.push_back({obj->GetName(), reinterpret_cast<UFont*>(obj)});
}
}

/// The cached fonts, building them on first use.
inline const std::vector<Entry>& Get()
{
if (!built) Rebuild();
return fonts;
}

/// Resolve a font by its short name; nullptr for an empty/unknown name (→ the renderer's default).
inline UFont* Find(const std::string& name)
{
if (name.empty()) return nullptr;
for (const Entry& e : Get())
if (e.name == name) return e.font;
return nullptr;
}
} // namespace FontCache
2 changes: 1 addition & 1 deletion Internal/features/AnnounceToggles.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class AnnounceToggles : public Feature
AnnounceToggles()
{
Name = "AnnounceToggles";
Event = Events::Type::SettingsChanged; // driven by menu changes, not per-frame
Triggers = {Events::Type::SettingsChanged}; // driven by menu changes, not per-frame
UpdateEnabled();
Log("Created");
};
Expand Down
21 changes: 9 additions & 12 deletions Internal/features/DiscordPresence.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@

/// @file
/// The DiscordPresence feature: refreshes the Discord Rich Presence with live game state (map +
/// K/D) on a throttle, so the presence stays current without spamming Discord every frame.
/// K/D). Event-driven rather than per-frame — it runs on the in-game/in-lobby transitions
/// (EnteredGame / EnteredLobby) and on each kill (PlayerKilled) so the K/D stays current.
/// UpdateGameState only pushes to Discord when the string actually changes, so PlayerKilled
/// (which fires for every kill in the match) effectively updates only on the local player's
/// own kills/deaths.

#include "Feature.h"
#include "../discord/rpc.h"

#include <chrono>

class DiscordPresence : public Feature
{
private:
std::chrono::steady_clock::time_point lastUpdate{};
static constexpr int IntervalSeconds = 5;

public:
DiscordPresence()
{
Name = "DiscordPresence";
Triggers = {Events::Type::EnteredGame, Events::Type::EnteredLobby, Events::Type::PlayerKilled};
UpdateEnabled();
Log("Created");
};
Expand All @@ -42,12 +41,10 @@ class DiscordPresence : public Feature
void Destroy() {
};

void Run()
/// Push the current game state to Discord on the event that fired us (a state transition or a
/// kill). UpdateGameState no-ops when the resulting presence string is unchanged.
void Run() override
{
const auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate).count() < IntervalSeconds) return;

lastUpdate = now;
DiscordRPC::UpdateGameState();
};
};
39 changes: 31 additions & 8 deletions Internal/features/Feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
/// drive it. See the class comment below for the full Init/Check/Run/Destroy
/// contract.

#include <vector>
#include <chrono>

#include "../settings/Settings.h"
#include "../utils/Logger.h"
#include "../scripting/Events.h"
Expand Down Expand Up @@ -34,19 +37,33 @@ class Feature
bool Enabled = false; // toggled from settings via UpdateEnabled()
bool Initialized = false; ///< set true by Init() once setup succeeds; features guard on this
bool OneTime = false; // Run() fires once per enable instead of every frame
int ThrottleMs = 0; ///< minimum gap between Run() calls in ms; 0 = every tick/event

std::string Name = "BaseFeature"; ///< human-readable id, used in Log() output
// Which event drives this feature. Render runs every frame (the fast loop in
// Features::Execute); any other event subscribes it to the event bus and runs
// it when that event is dispatched (e.g. Events::Type::PlayerDeath).
Events::Type Event = Events::Type::Render;
// Which events drive this feature. Render runs every frame (the fast loop in
// Features::Execute); every other trigger subscribes it to the event bus and runs it when that
// event is dispatched (e.g. Events::Type::PlayerDeath). A feature may list several triggers, and
// may mix Render with bus events.
std::vector<Events::Type> Triggers{Events::Type::Render};

// Bookkeeping owned by Features::Execute; subclasses should not touch these.
bool applied = false; // Run() has been applied and not yet reverted by Destroy()
bool hasRun = false; // a OneTime feature has already run this enable cycle
bool applied = false; // Run() has been applied and not yet reverted by Destroy()
bool hasRun = false; // a OneTime feature has already run this enable cycle
std::chrono::steady_clock::time_point lastRun{}; // when Run() last fired, for ThrottleMs

Feature() {};

/// Whether the throttle interval has elapsed since the last Run() (and, if so, stamps it now).
/// Always true when ThrottleMs <= 0. Owned by the runner; features don't call this.
bool ThrottleReady()
{
if (ThrottleMs <= 0) return true;
const auto now = std::chrono::steady_clock::now();
if (now - lastRun < std::chrono::milliseconds(ThrottleMs)) return false;
lastRun = now;
return true;
}

/// One-time setup (resolve game objects, cache originals). Must set
/// Initialized to reflect success; the runner calls it on first use.
virtual void Init() = 0;
Expand All @@ -58,8 +75,14 @@ class Feature
virtual bool Check() = 0;
/// Revert whatever Run() applied. Called once on the enabled -> disabled edge.
virtual void Destroy() = 0;
/// Apply the effect. Called every frame while enabled (or once per enable if OneTime).
virtual void Run() = 0;

/// Apply the effect. Called every frame while enabled (or once per enable if OneTime) for a
/// Render trigger, and on each dispatch of any other trigger event. The runner always calls the
/// (event, payload) form; the default chain lets a subclass override whichever arity it needs —
/// Run() to ignore the trigger, Run(event) for the event only, or Run(event, payload) for both.
virtual void Run() {}
virtual void Run(Events::Type) { Run(); }
virtual void Run(Events::Type event, const Events::Payload&) { Run(event); }

/// Emit a "[Name] message" line, but only when Settings.DEBUG.FeaturesLogging is on.
void Log(std::string message)
Expand Down
21 changes: 12 additions & 9 deletions Internal/features/FeatureRunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <vector>
#include <memory>
#include <algorithm>

#include "Feature.h"

Expand All @@ -24,10 +25,11 @@ namespace Features
// Drives a single feature once: init on first use, refresh Enabled, skip if
// idle-disabled, then Run() while enabled (once, if OneTime) or Destroy()
// exactly once on the enabled -> disabled edge. Used both by the per-frame
// render loop and by the event bus (for event-driven features). Features
// whose Check() still returns Enabled keep their previous behavior (they just
// skip on disable and never reach Destroy).
inline void RunFeature(Feature& feature)
// render loop and by the event bus (for event-driven features). The triggering
// @p event and @p payload are forwarded to Run() (defaulting to the Render loop's
// empty payload). Features whose Check() still returns Enabled keep their previous
// behavior (they just skip on disable and never reach Destroy).
inline void RunFeature(Feature& feature, Events::Type event = Events::Type::Render, const Events::Payload& payload = {})
{
try
{
Expand All @@ -53,9 +55,9 @@ namespace Features

if (feature.Enabled)
{
if (!feature.OneTime || !feature.hasRun)
if ((!feature.OneTime || !feature.hasRun) && feature.ThrottleReady())
{
feature.Run();
feature.Run(event, payload);
feature.hasRun = true;
}
feature.applied = true;
Expand All @@ -77,13 +79,14 @@ namespace Features
}

// Runs every render-driven feature. Called once per rendered frame from
// PostRender. Event-driven features (Event != "render") are skipped here and
// run from the event bus instead.
// PostRender. Features without a Render trigger are skipped here and run from
// the event bus instead (a feature may have both).
inline void Execute()
{
for (const auto& feature : Features)
{
if (feature->Event == Events::Type::Render)
const auto& triggers = feature->Triggers;
if (std::find(triggers.begin(), triggers.end(), Events::Type::Render) != triggers.end())
{
RunFeature(*feature);
}
Expand Down
12 changes: 7 additions & 5 deletions Internal/features/Features.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,16 @@ namespace Features
// Autosave: persist on every change when enabled.
if (Settings.MISC.AutoSave) SettingsHelper::File().Save(); });

// Subscribe event-driven features (Event != "render") to the event bus;
// render features run from Features::Execute each frame instead.
// Subscribe each non-Render trigger to the event bus, forwarding the event + payload to the
// feature's Run(); Render triggers run from Features::Execute each frame instead. A feature
// may list several triggers (e.g. DiscordPresence on EnteredGame + EnteredLobby).
for (auto& feature : Features)
{
if (feature->Event != Events::Type::Render)
for (Events::Type trigger : feature->Triggers)
{
Events::Register(feature->Event, [ptr = feature.get()]
{ RunFeature(*ptr); });
if (trigger == Events::Type::Render) continue;
Events::Register(trigger, [ptr = feature.get(), trigger](const Events::Payload& payload)
{ RunFeature(*ptr, trigger, payload); });
}
}

Expand Down
Loading