Rewrite Renderer (#1084)

* Rewrite Renderer

* some fixes
This commit is contained in:
Arthur
2026-08-17 12:29:04 +03:00
committed by GitHub
parent 36263375cf
commit fdb1a043c0
17 changed files with 1020 additions and 701 deletions
+3 -1
View File
@@ -1,6 +1,8 @@
#include "HotkeySystem.hpp"
#include "core/backend/FiberPool.hpp"
#include "core/backend/ScriptMgr.hpp"
#include "core/renderer/D3D12Hook.hpp"
#include "core/renderer/Renderer.hpp"
#include "Commands.hpp"
#include "LoopedCommand.hpp"
#include "core/util/Joaat.hpp"
@@ -95,7 +97,7 @@ namespace YimMenu
{
while (g_Running)
{
if (GetForegroundWindow() == *Pointers.Hwnd && !HUD::IS_PAUSE_MENU_ACTIVE() && !HUD::IS_SOCIAL_CLUB_ACTIVE() && !m_BeingModified && !GUI::IsUsingKeyboard())
if (GetForegroundWindow() == D3D12Hook::GetWindow() && !HUD::IS_PAUSE_MENU_ACTIVE() && !HUD::IS_SOCIAL_CLUB_ACTIVE() && !m_BeingModified && (Renderer::IsInitialized() && !GUI::IsUsingKeyboard()))
{
for (auto& [hash, link] : m_CommandHotkeys)
{
-6
View File
@@ -10,14 +10,8 @@ namespace YimMenu
{
Hooking::Hooking()
{
BaseHook::Add<Hooks::Window::WndProc>(new DetourHook("WndProc", Pointers.WndProc, Hooks::Window::WndProc));
BaseHook::Add<Hooks::RawInput::GetRawInputData>(new DetourHook("GetRawInputData", reinterpret_cast<void*>(GetProcAddress(LoadLibraryA("user32.dll"), "GetRawInputData")), Hooks::RawInput::GetRawInputData));
auto swapchain_vft = *reinterpret_cast<void***>(*Pointers.SwapChain);
BaseHook::Add<Hooks::SwapChain::Present>(new DetourHook("Present", swapchain_vft[Hooks::SwapChain::VMTPresentIdx], Hooks::SwapChain::Present));
BaseHook::Add<Hooks::SwapChain::ResizeBuffers>(new DetourHook("ResizeBuffers", swapchain_vft[Hooks::SwapChain::VMTResizeBuffersIdx], Hooks::SwapChain::ResizeBuffers));
// BaseHook::Add<Hooks::Anticheat::QueueDependency>(new DetourHook("QueueDependency", Pointers.QueueDependency, Hooks::Anticheat::QueueDependency));
BaseHook::Add<Hooks::Anticheat::GameSkeletonUpdate>(new DetourHook("GameSkeletonUpdate", Pointers.GameSkeletonUpdate, Hooks::Anticheat::GameSkeletonUpdate));
BaseHook::Add<Hooks::Anticheat::PrepareMetricForSending>(new DetourHook("PrepareMetricForSending", Pointers.PrepareMetricForSending, Hooks::Anticheat::PrepareMetricForSending));
+433
View File
@@ -0,0 +1,433 @@
#include "D3D12Hook.hpp"
namespace YimMenu
{
static thread_local bool g_InsideD3d12Hook = false;
bool D3D12Hook::RawVtablePatch::Install(void** vtable, size_t index, void* newFn)
{
if (!vtable)
return false;
Slot = &vtable[index];
OriginalFn = *Slot;
DWORD oldProtect{};
if (!VirtualProtect(Slot, sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProtect)) // we still need this in case the menu is injected very early
return false;
*Slot = newFn;
VirtualProtect(Slot, sizeof(void*), oldProtect, &oldProtect);
Installed = true;
return true;
}
void D3D12Hook::RawVtablePatch::Uninstall()
{
if (!Installed || !Slot)
return;
DWORD oldProtect{};
if (VirtualProtect(Slot, sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProtect))
{
*Slot = OriginalFn;
VirtualProtect(Slot, sizeof(void*), oldProtect, &oldProtect);
}
Installed = false;
Slot = nullptr;
OriginalFn = nullptr;
}
bool D3D12Hook::InitImpl()
{
std::scoped_lock hookLock{m_HookMutex};
g_InsideD3d12Hook = true;
struct Restore
{
~Restore()
{
g_InsideD3d12Hook = false;
}
} restore{};
// if this is a rehook after a swapchain recreation
if (m_CommandQueueOffset != 0 && m_SwapchainVtable && m_FactoryVtable)
{
LOGF(INFO, "Re-initializing via previously discovered vtables.");
InstallHooks();
return m_Hooked;
}
IDXGISwapChain1* swapChain1 = nullptr;
IDXGISwapChain3* swapChain = nullptr;
ID3D12Device* device = nullptr;
const D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc1{};
swapChainDesc1.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDesc1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDesc1.BufferCount = 2;
swapChainDesc1.SampleDesc.Count = 1;
swapChainDesc1.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
swapChainDesc1.Width = 1;
swapChainDesc1.Height = 1;
auto d3d12Module = LoadLibraryA("d3d12.dll");
if (!d3d12Module)
{
LOGF(FATAL, "Failed to load d3d12.dll.");
return false;
}
auto d3d12CreateDevice = reinterpret_cast<decltype(D3D12CreateDevice)*>(GetProcAddress(d3d12Module, "D3D12CreateDevice"));
if (!d3d12CreateDevice)
{
LOGF(FATAL, "Failed to resolve D3D12CreateDevice.");
return false;
}
bool deviceCreated = !FAILED(d3d12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&device)));
if (!deviceCreated)
{
LOGF(FATAL, "Failed to create dummy D3D12 device.");
return false;
}
auto dxgiModule = LoadLibraryA("dxgi.dll");
if (!dxgiModule)
{
LOGF(FATAL, "Failed to load dxgi.dll.");
device->Release();
return false;
}
auto createDxgiFactory = reinterpret_cast<decltype(CreateDXGIFactory)*>(GetProcAddress(dxgiModule, "CreateDXGIFactory"));
if (!createDxgiFactory)
{
LOGF(FATAL, "Failed to resolve CreateDXGIFactory.");
device->Release();
return false;
}
IDXGIFactory4* factory = nullptr;
if (FAILED(createDxgiFactory(IID_PPV_ARGS(&factory))))
{
LOGF(FATAL, "Failed to create the dummy DXGI factory.");
device->Release();
return false;
}
D3D12_COMMAND_QUEUE_DESC queueDesc{};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ID3D12CommandQueue* commandQueue = nullptr;
if (FAILED(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue))))
{
LOGF(FATAL, "Failed to create the dummy command queue.");
factory->Release();
device->Release();
return false;
}
HWND hwnd = nullptr;
WNDCLASSEXA wc{};
auto initDummyWindow = [&]() {
wc.cbSize = sizeof(WNDCLASSEXA);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = DefWindowProcA;
wc.hInstance = GetModuleHandleA(nullptr);
wc.lpszClassName = "YimMenuDummyWindow";
RegisterClassExA(&wc);
hwnd = CreateWindowA(wc.lpszClassName, "YimMenu Dummy Window", WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, nullptr, nullptr, wc.hInstance, nullptr);
swapChainDesc1.BufferCount = 3;
swapChainDesc1.Width = 0;
swapChainDesc1.Height = 0;
swapChainDesc1.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc1.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
swapChainDesc1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc1.SampleDesc.Count = 1;
swapChainDesc1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc1.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
swapChainDesc1.Scaling = DXGI_SCALING_STRETCH;
};
const std::vector<std::function<bool()>> swapchainAttempts{
[&]() {
return !FAILED(factory->CreateSwapChainForComposition(commandQueue, &swapChainDesc1, nullptr, &swapChain1));
},
[&]() {
initDummyWindow();
return !FAILED(factory->CreateSwapChainForHwnd(commandQueue, hwnd, &swapChainDesc1, nullptr, nullptr, &swapChain1));
},
[&]() {
return !FAILED(factory->CreateSwapChainForHwnd(commandQueue, GetDesktopWindow(), &swapChainDesc1, nullptr, nullptr, &swapChain1));
},
};
bool anySucceeded = false;
for (size_t i = 0; i < swapchainAttempts.size(); i++)
{
try
{
if (swapchainAttempts[i]())
{
LOGF(INFO, "Created dummy swapchain via attempt {}.", i);
anySucceeded = true;
break;
}
}
catch (const std::exception& e)
{
LOGF(FATAL, "Dummy swapchain attempt {} threw: {}", i, e.what());
}
catch (...)
{
LOGF(FATAL, "Dummy swapchain attempt {} threw an unknown exception.", i);
}
}
auto cleanupDummyWindow = [&]() {
if (hwnd)
DestroyWindow(hwnd);
if (wc.lpszClassName)
UnregisterClassA(wc.lpszClassName, wc.hInstance);
};
if (!anySucceeded)
{
LOGF(FATAL, "Failed to create a dummy swapchain via any method.");
cleanupDummyWindow();
commandQueue->Release();
factory->Release();
device->Release();
return false;
}
if (FAILED(swapChain1->QueryInterface(IID_PPV_ARGS(&swapChain))))
{
LOGF(FATAL, "Failed to query IDXGISwapChain3 from the dummy swapchain.");
cleanupDummyWindow();
swapChain1->Release();
commandQueue->Release();
factory->Release();
device->Release();
return false;
}
m_CommandQueueOffset = 0;
for (uint32_t i = 0; i < 512 * sizeof(void*); i += sizeof(void*))
{
auto base = reinterpret_cast<uintptr_t>(swapChain1) + i;
if (IsBadReadPtr(reinterpret_cast<void*>(base), sizeof(void*)))
break;
if (*reinterpret_cast<ID3D12CommandQueue**>(base) == commandQueue)
{
m_CommandQueueOffset = i;
LOGF(INFO, "Found command queue offset at {:#x}.", i);
break;
}
}
auto targetSwapchain = swapChain;
if (m_CommandQueueOffset == 0)
{
bool shouldBreak = false;
for (uint32_t base = 0; base < 512 * sizeof(void*) && !shouldBreak; base += sizeof(void*))
{
auto preScanBase = reinterpret_cast<uintptr_t>(swapChain1) + base;
if (IsBadReadPtr(reinterpret_cast<void*>(preScanBase), sizeof(void*)))
break;
auto scanBase = *reinterpret_cast<uintptr_t*>(preScanBase);
if (scanBase == 0 || IsBadReadPtr(reinterpret_cast<void*>(scanBase), sizeof(void*)))
continue;
for (uint32_t i = 0; i < 512 * sizeof(void*); i += sizeof(void*))
{
auto preData = scanBase + i;
if (IsBadReadPtr(reinterpret_cast<void*>(preData), sizeof(void*)))
break;
if (*reinterpret_cast<ID3D12CommandQueue**>(preData) == commandQueue)
{
targetSwapchain = reinterpret_cast<IDXGISwapChain3*>(scanBase);
m_CommandQueueOffset = i;
shouldBreak = true;
LOGF(INFO, "Found nested command queue offset at {:#x}.", i);
break;
}
}
}
}
if (m_CommandQueueOffset == 0)
{
LOGF(FATAL, "Failed to find the command queue offset.");
cleanupDummyWindow();
swapChain->Release();
swapChain1->Release();
commandQueue->Release();
factory->Release();
device->Release();
return false;
}
m_SwapchainVtable = *reinterpret_cast<void***>(targetSwapchain);
m_FactoryVtable = *reinterpret_cast<void***>(factory);
InstallHooks();
commandQueue->Release();
swapChain1->Release();
swapChain->Release();
device->Release();
factory->Release();
cleanupDummyWindow();
if (!m_Hooked)
return false;
LOGF(INFO, "D3D12 Hook initialized.");
return true;
}
bool D3D12Hook::DestroyImpl(bool unloading)
{
std::scoped_lock hookLock{m_HookMutex};
if (!m_Hooked)
return true;
m_PresentPatch.Uninstall();
m_SwapchainVmt.reset();
if (unloading)
m_CreateSwapchainPatch.Uninstall();
m_Hooked = false;
m_IsPhase1 = true;
return true;
}
void D3D12Hook::InstallHooks()
{
m_PresentPatch.Uninstall();
m_SwapchainVmt.reset();
m_IsPhase1 = true;
if (!m_PresentPatch.Install(m_SwapchainVtable, 8, reinterpret_cast<void*>(&D3D12Hook::Present)))
{
LOGF(FATAL, "Failed to install the phase 1 Present vtable patch.");
m_Hooked = false;
return;
}
if (!m_CreateSwapchainPatch.Installed)
{
if (!m_CreateSwapchainPatch.Install(m_FactoryVtable, 15, reinterpret_cast<void*>(&D3D12Hook::CreateSwapchain)))
LOGF(FATAL, "Failed to install the CreateSwapChain patch.");
}
m_Hooked = true;
}
HRESULT D3D12Hook::Present(IDXGISwapChain3* swapChain, UINT syncInterval, UINT flags, void* r9)
{
std::scoped_lock hookLock{GetInstance().m_HookMutex};
auto presentFn = GetInstance().m_IsPhase1 ? GetInstance().m_PresentPatch.Original<decltype(&D3D12Hook::Present)>() : GetInstance().m_SwapchainVmt->Original<decltype(&D3D12Hook::Present)>(8);
HWND swapchainWnd = nullptr;
{
DXGI_SWAP_CHAIN_DESC swapchainDesc{};
if (SUCCEEDED(swapChain->GetDesc(&swapchainDesc)))
swapchainWnd = swapchainDesc.OutputWindow;
}
if (!GetInstance().m_IsPhase1 && swapChain != GetInstance().m_SwapChain)
return presentFn(swapChain, syncInterval, flags, r9);
if (GetInstance().m_IsPhase1) // caught the real present, switch to per instance vmt hook
{
GetInstance().m_PresentPatch.Uninstall();
GetInstance().m_SwapchainVmt = std::make_unique<VMTHook>("D3D12SwapChain", swapChain, 64); // should be enough
GetInstance().m_SwapchainVmt->Hook(8, reinterpret_cast<void*>(&D3D12Hook::Present));
GetInstance().m_SwapchainVmt->Hook(13, reinterpret_cast<void*>(&D3D12Hook::ResizeBuffers));
GetInstance().m_SwapchainVmt->Enable();
GetInstance().m_IsPhase1 = false;
presentFn = GetInstance().m_SwapchainVmt->Original<decltype(&D3D12Hook::Present)>(8);
}
GetInstance().m_Window = swapchainWnd;
GetInstance().m_SwapChain = swapChain;
{
Microsoft::WRL::ComPtr<ID3D12Device4> tempDevice{};
swapChain->GetDevice(IID_PPV_ARGS(&tempDevice));
GetInstance().m_Device = tempDevice.Get();
}
GetInstance().m_CommandQueue = *reinterpret_cast<ID3D12CommandQueue**>(reinterpret_cast<uintptr_t>(swapChain) + GetInstance().m_CommandQueueOffset);
if (GetInstance().m_OnPresent)
GetInstance().m_OnPresent();
return presentFn(swapChain, syncInterval, flags, r9);
}
HRESULT D3D12Hook::ResizeBuffers(IDXGISwapChain3* swapChain, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags)
{
std::scoped_lock hookLock{GetInstance().m_HookMutex};
auto resizeBuffersFn = GetInstance().m_SwapchainVmt->Original<decltype(&D3D12Hook::ResizeBuffers)>(13);
if (GetInstance().m_OnResizeBuffers)
GetInstance().m_OnResizeBuffers();
return resizeBuffersFn(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
}
HRESULT D3D12Hook::CreateSwapchain(IDXGIFactory4* factory, IUnknown* device, HWND hwnd, const DXGI_SWAP_CHAIN_DESC* desc, const DXGI_SWAP_CHAIN_FULLSCREEN_DESC* pFullscreenDesc, IDXGIOutput* pRestrictToOutput, IDXGISwapChain** swapChain)
{
auto createSwapChainFn = GetInstance().m_CreateSwapchainPatch.Original<decltype(&D3D12Hook::CreateSwapchain)>();
if (g_InsideD3d12Hook)
return createSwapChainFn(factory, device, hwnd, desc, pFullscreenDesc, pRestrictToOutput, swapChain);
LOGF(INFO, "CreateSwapChain called, a new swapchain is being created.");
std::scoped_lock hookLock{GetInstance().m_HookMutex};
bool wasHooked = GetInstance().m_Hooked;
if (wasHooked)
{
if (GetInstance().m_OnDeviceReset)
GetInstance().m_OnDeviceReset();
GetInstance().Destroy();
}
if (wasHooked)
GetInstance().Init();
return createSwapChainFn(factory, device, hwnd, desc, pFullscreenDesc, pRestrictToOutput, swapChain);
}
}
+111
View File
@@ -0,0 +1,111 @@
// Credits to:
// https://github.com/praydog/REFramework/blob/master/src/D3D12Hook.cpp
// https://github.com/praydog/REFramework/blob/master/src/D3D12Hook.hpp
#pragma once
#include "core/hooking/VMTHook.hpp"
#include <d3d12.h>
#include <dxgi1_4.h>
#include <wrl/client.h>
namespace YimMenu
{
class D3D12Hook
{
public:
static bool Init()
{
return GetInstance().InitImpl();
}
static bool Destroy(bool unloading = false)
{
return GetInstance().DestroyImpl(unloading);
}
static void OnPresent(std::function<void()> fn)
{
GetInstance().m_OnPresent = std::move(fn);
}
static void OnResizeBuffers(std::function<void()> fn)
{
GetInstance().m_OnResizeBuffers = std::move(fn);
}
static void OnDeviceReset(std::function<void()> fn)
{
GetInstance().m_OnDeviceReset = std::move(fn);
}
static ID3D12Device4* GetDevice()
{
return GetInstance().m_Device;
}
static IDXGISwapChain3* GetSwapChain()
{
return GetInstance().m_SwapChain;
}
static ID3D12CommandQueue* GetCommandQueue()
{
return GetInstance().m_CommandQueue;
}
static HWND GetWindow()
{
return GetInstance().m_Window;
}
private:
// VMTHook clones the vtable per-instance (so hooking one object never
// affects another), which is exactly the opposite of what phase 1 needs, so
// this one raw patch is done by hand.
struct RawVtablePatch
{
bool Install(void** vtable, size_t index, void* newFn);
void Uninstall();
template <typename T>
T Original() const
{
return reinterpret_cast<T>(OriginalFn);
}
void** Slot = nullptr;
void* OriginalFn = nullptr;
bool Installed = false;
};
static D3D12Hook& GetInstance()
{
static D3D12Hook instance;
return instance;
}
bool InitImpl();
bool DestroyImpl(bool unloading);
void InstallHooks();
static HRESULT Present(IDXGISwapChain3* swapChain, UINT syncInterval, UINT flags, void* r9);
static HRESULT ResizeBuffers(IDXGISwapChain3* swapChain, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags);
static HRESULT CreateSwapchain(IDXGIFactory4* factory, IUnknown* device, HWND hwnd, const DXGI_SWAP_CHAIN_DESC* desc, const DXGI_SWAP_CHAIN_FULLSCREEN_DESC* pFullscreenDesc, IDXGIOutput* pRestrictToOutput, IDXGISwapChain** swapChain);
std::function<void()> m_OnPresent = nullptr;
std::function<void()> m_OnResizeBuffers = nullptr;
std::function<void()> m_OnDeviceReset = nullptr;
ID3D12Device4* m_Device = nullptr;
ID3D12CommandQueue* m_CommandQueue = nullptr;
IDXGISwapChain3* m_SwapChain = nullptr;
HWND m_Window = nullptr;
bool m_Hooked = false;
bool m_IsPhase1 = true;
uint32_t m_CommandQueueOffset = 0;
void** m_FactoryVtable = nullptr;
void** m_SwapchainVtable = nullptr;
RawVtablePatch m_CreateSwapchainPatch = {};
RawVtablePatch m_PresentPatch = {};
std::unique_ptr<VMTHook> m_SwapchainVmt = {};
std::recursive_mutex m_HookMutex = {};
};
}
+361 -364
View File
@@ -1,394 +1,391 @@
#include "Renderer.hpp"
#include "core/memory/ModuleMgr.hpp"
#include "core/memory/PatternScanner.hpp"
#include "game/frontend/GUI.hpp"
#include "D3D12Hook.hpp"
#include "core/frontend/manager/styles/Themes.hpp"
#include "game/frontend/Menu.hpp"
#include "game/pointers/Pointers.hpp"
#include <backends/imgui_impl_dx12.h>
#include <backends/imgui_impl_win32.h>
#include <imgui.h>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
namespace YimMenu
{
Renderer::Renderer() :
m_Initialized(false),
m_Resizing(false),
m_FontsUpdated(false),
m_SafeToRender(false)
{
}
void Renderer::InitImpl()
{
D3D12Hook::OnPresent([this] {
if (!m_Initialized)
{
if (!InitInternal())
return;
}
Renderer::~Renderer()
{
}
Render();
});
void Renderer::DestroyImpl()
{
if (!m_Initialized)
return;
D3D12Hook::OnResizeBuffers([this] {
Resize();
});
// TODO: we aren't destroying resources properly
ImGui_ImplWin32_Shutdown();
D3D12Hook::OnDeviceReset([this] {
Destroy();
});
}
bool Renderer::InitInternal()
{
auto device = D3D12Hook::GetDevice();
auto swapChain = D3D12Hook::GetSwapChain();
auto queue = D3D12Hook::GetCommandQueue();
auto window = D3D12Hook::GetWindow();
if (!device || !swapChain || !queue || !window)
{
//LOGF(FATAL, "Invalid D3D12Hook data.");
return false;
}
WaitForLastFrame();
ImGui_ImplDX12_InvalidateDeviceObjects();
DXGI_SWAP_CHAIN_DESC desc{};
swapChain->GetDesc(&desc);
UINT bufferCount = desc.BufferCount > 0 ? desc.BufferCount : 3;
for (size_t i{}; i != GetInstance().m_SwapChainDesc.BufferCount; ++i)
{
REL(GetInstance().m_FrameContext[i].Resource);
}
if (!CreateDeviceObjects(device, queue, bufferCount))
{
LOGF(FATAL, "Failed to create device objects.");
return false;
}
ImGui_ImplDX12_Shutdown();
CreateRenderTargets(swapChain);
ImGui::DestroyContext();
m_Window = window;
m_OriginalWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtrA(m_Window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&Renderer::WndProc)));
#if 0
// manually destroy the allocators we created for the rest of the frame contexts
for (size_t i = 1; i < m_SwapChainDesc.BufferCount; ++i)
if (m_FrameContext[i].CommandAllocator)
m_FrameContext[i].CommandAllocator->Release();
#endif
}
ImGui::CreateContext();
bool Renderer::InitDX12()
{
if (!Pointers.SwapChain)
{
LOG(WARNING) << "SwapChain pointer is invalid!";
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui::StyleColorsDark();
ImGui_ImplWin32_Init(m_Window);
ImGui_ImplDX12_InitInfo initInfo{};
initInfo.Device = device;
initInfo.CommandQueue = queue;
initInfo.NumFramesInFlight = static_cast<int>(bufferCount);
initInfo.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
initInfo.DSVFormat = DXGI_FORMAT_UNKNOWN;
initInfo.SrvDescriptorHeap = m_SrvHeap.Get();
initInfo.UserData = &m_SrvAllocator;
initInfo.SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo* info, D3D12_CPU_DESCRIPTOR_HANDLE* outCpu, D3D12_GPU_DESCRIPTOR_HANDLE* outGpu) {
static_cast<SrvHeapAllocator*>(info->UserData)->Alloc(outCpu, outGpu);
};
initInfo.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo* info, D3D12_CPU_DESCRIPTOR_HANDLE cpu, D3D12_GPU_DESCRIPTOR_HANDLE gpu) {
static_cast<SrvHeapAllocator*>(info->UserData)->Free(cpu, gpu);
};
if (!ImGui_ImplDX12_Init(&initInfo))
{
LOGF(FATAL, "ImGui_ImplDX12_Init failed.");
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
if (m_Window && m_OriginalWndProc)
SetWindowLongPtrA(m_Window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_OriginalWndProc));
m_Window = nullptr;
m_OriginalWndProc = nullptr;
return false;
}
}
if (!Pointers.CommandQueue)
Menu::SetupFonts();
SetupStyle();
GUI::ToggleMouse();
m_Initialized = true;
LOGF(INFO, "Renderer initialized.");
return true;
}
void Renderer::DestroyImpl()
{
if (!m_Initialized && !m_DeviceObjectsValid)
return;
ReleaseRenderTargets();
if (m_Initialized)
{
ImGui_ImplDX12_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
if (m_Window && m_OriginalWndProc)
SetWindowLongPtrA(m_Window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_OriginalWndProc));
if (m_FenceEvent)
{
CloseHandle(m_FenceEvent);
m_FenceEvent = nullptr;
}
m_FrameContexts.clear();
m_BackBuffers.clear();
m_RtvHandles.clear();
m_CommandList.Reset();
m_Fence.Reset();
m_RtvHeap.Reset();
m_SrvHeap.Reset();
m_Initialized = false;
m_DeviceObjectsValid = false;
m_Window = nullptr;
m_OriginalWndProc = nullptr;
}
void Renderer::Render()
{
auto device = D3D12Hook::GetDevice();
auto swapChain = D3D12Hook::GetSwapChain();
auto queue = D3D12Hook::GetCommandQueue();
if (!swapChain)
return;
// if a resize happened, our cached backbuffer resources are stale, refresh them
ComPtr<ID3D12Resource> currentAtIndex{};
auto index = swapChain->GetCurrentBackBufferIndex();
if (index >= m_BackBuffers.size() || m_BackBuffers[index] == nullptr)
{
CreateRenderTargets(swapChain);
}
else
{
swapChain->GetBuffer(index, IID_PPV_ARGS(&currentAtIndex));
if (currentAtIndex.Get() != m_BackBuffers[index].Get())
CreateRenderTargets(swapChain);
}
auto& frame = m_FrameContexts[index];
// wait for the GPU to be done with this frame context's allocator before reusing it
if (m_Fence->GetCompletedValue() < frame.FenceValue)
{
m_Fence->SetEventOnCompletion(frame.FenceValue, m_FenceEvent);
WaitForSingleObject(m_FenceEvent, INFINITE);
}
frame.Allocator->Reset();
m_CommandList->Reset(frame.Allocator.Get(), nullptr);
if (m_SafeToRender)
{
D3D12_RESOURCE_BARRIER toRt{};
toRt.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
toRt.Transition.pResource = m_BackBuffers[index].Get();
toRt.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
toRt.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
toRt.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
m_CommandList->ResourceBarrier(1, &toRt);
m_CommandList->OMSetRenderTargets(1, &m_RtvHandles[index], FALSE, nullptr);
ID3D12DescriptorHeap* heaps[] = {m_SrvHeap.Get()};
m_CommandList->SetDescriptorHeaps(1, heaps);
if (m_FontsUpdated)
{
InvalidateDeviceObjects();
DXGI_SWAP_CHAIN_DESC desc{};
swapChain->GetDesc(&desc);
UINT bufferCount = desc.BufferCount > 0 ? desc.BufferCount : 3;
CreateDeviceObjects(device, queue, bufferCount);
ImGui_ImplDX12_CreateDeviceObjects();
m_FontsUpdated = false;
}
ImGui_ImplDX12_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
for (const auto& callback : m_RendererCallbacks | std::views::values)
callback();
ImGui::Render();
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), m_CommandList.Get());
D3D12_RESOURCE_BARRIER toPresent{};
toPresent.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
toPresent.Transition.pResource = m_BackBuffers[index].Get();
toPresent.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
toPresent.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
toPresent.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
m_CommandList->ResourceBarrier(1, &toPresent);
m_CommandList->Close();
ID3D12CommandList* lists[] = {m_CommandList.Get()};
D3D12Hook::GetCommandQueue()->ExecuteCommandLists(1, lists);
++m_FenceValue;
D3D12Hook::GetCommandQueue()->Signal(m_Fence.Get(), m_FenceValue);
frame.FenceValue = m_FenceValue;
}
else
{
LOG(WARNING) << "CommandQueue pointer is invalid!";
return false;
m_CommandList->Close();
}
}
void Renderer::Resize()
{
if (!m_Initialized)
return;
//This is required. In order to stop ComPtr from releasing the original pointer, we create a new ComPtr with the ptr as the intializer. (The '=' operator uses swap which releases the object passed into it)
if (m_GameSwapChain = ComPtr<IDXGISwapChain1>(*Pointers.SwapChain); !m_GameSwapChain.Get())
{
LOG(WARNING) << "Dereferenced SwapChain pointer is invalid!";
ReleaseRenderTargets();
}
bool Renderer::CreateDeviceObjects(ID3D12Device* device, ID3D12CommandQueue* queue, UINT bufferCount)
{
m_BufferCount = bufferCount;
return false;
}
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc{};
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvDesc.NumDescriptors = bufferCount;
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
if (m_CommandQueue = ComPtr<ID3D12CommandQueue>(*Pointers.CommandQueue); !m_CommandQueue.Get())
{
LOG(WARNING) << "Dereferenced CommandQueue pointer is invalid!";
if (FAILED(device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&m_RtvHeap))))
return false;
return false;
}
m_GameSwapChain.As(&m_SwapChain); //We need GetCurrentBackBufferIndex from IDXGISwapChain3
D3D12_DESCRIPTOR_HEAP_DESC srvDesc{};
srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvDesc.NumDescriptors = 64;
srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
if (const auto result = m_SwapChain->GetDevice(__uuidof(ID3D12Device), reinterpret_cast<void**>(m_Device.GetAddressOf())); result < 0)
{
LOG(WARNING) << "Failed to get D3D Device with result: [" << result << "]";
return false;
}
if (const auto result = m_SwapChain->GetDesc(&m_SwapChainDesc); result < 0)
{
LOG(WARNING) << "Failed to get SwapChain Description with result: [" << result << "]";
return false;
}
if (const auto result = m_Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), (void**)m_Fence.GetAddressOf()); result < 0)
{
LOG(WARNING) << "Failed to create Fence with result: [" << result << "]";
return false;
}
if (const auto result = m_FenceEvent = CreateEventA(nullptr, FALSE, FALSE, nullptr); !result)
{
LOG(WARNING) << "Failed to create Fence Event!";
return false;
}
m_FrameContext.resize(m_SwapChainDesc.BufferCount);
D3D12_DESCRIPTOR_HEAP_DESC DescriptorDesc{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, m_SwapChainDesc.BufferCount, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE};
if (const auto result =
m_Device->CreateDescriptorHeap(&DescriptorDesc, __uuidof(ID3D12DescriptorHeap), (void**)m_DescriptorHeap.GetAddressOf());
result < 0)
{
LOG(WARNING) << "Failed to create Descriptor Heap with result: [" << result << "]";
return false;
}
if (const auto result = m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
__uuidof(ID3D12CommandAllocator),
(void**)m_CommandAllocator.GetAddressOf());
result < 0)
{
LOG(WARNING) << "Failed to create primary Command Allocator with result: [" << result << "]";
return false;
}
m_FrameContext[0].CommandAllocator = m_CommandAllocator.Get(); // set initial command allocator
// create the rest of the allocators
for (size_t i = 1; i < m_SwapChainDesc.BufferCount; ++i)
{
if (const auto result = m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator), (void**)&m_FrameContext[i].CommandAllocator); result < 0)
{
LOG(WARNING) << "Failed to create secondary Command Allocator with result: [" << result << "]";
return false;
}
}
if (const auto result = m_Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_CommandAllocator.Get(), NULL, __uuidof(ID3D12GraphicsCommandList), (void**)m_CommandList.GetAddressOf()); result < 0)
{
LOG(WARNING) << "Failed to create Command List with result: [" << result << "]";
return false;
}
if (const auto result = m_CommandList->Close(); result < 0)
{
LOG(WARNING) << "Failed to finalize the creation of Command List with result: [" << result << "]";
return false;
}
D3D12_DESCRIPTOR_HEAP_DESC DescriptorBackbufferDesc{D3D12_DESCRIPTOR_HEAP_TYPE_RTV, m_SwapChainDesc.BufferCount, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1};
if (const auto result = m_Device->CreateDescriptorHeap(&DescriptorBackbufferDesc,
__uuidof(ID3D12DescriptorHeap),
(void**)m_BackbufferDescriptorHeap.GetAddressOf());
result < 0)
{
LOG(WARNING) << "Failed to create Backbuffer Descriptor Heap with result: [" << result << "]";
return false;
}
const auto RTVDescriptorSize{m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV)};
D3D12_CPU_DESCRIPTOR_HANDLE RTVHandle{m_BackbufferDescriptorHeap->GetCPUDescriptorHandleForHeapStart()};
for (size_t i = 0; i < m_SwapChainDesc.BufferCount; ++i)
{
ComPtr<ID3D12Resource> BackBuffer{};
m_FrameContext[i].Descriptor = RTVHandle;
m_SwapChain->GetBuffer(i, __uuidof(ID3D12Resource), (void**)BackBuffer.GetAddressOf());
m_Device->CreateRenderTargetView(BackBuffer.Get(), nullptr, RTVHandle);
m_FrameContext[i].Resource = BackBuffer.Get();
RTVHandle.ptr += RTVDescriptorSize;
}
m_HeapAllocator.Create(m_Device.Get(), m_DescriptorHeap.Get());
// never returns false, useless to check return
ImGui::CreateContext();
ImGui_ImplWin32_Init(*Pointers.Hwnd);
ImGui_ImplDX12_InitInfo init_info = {};
init_info.Device = m_Device.Get();
init_info.CommandQueue = m_CommandQueue.Get();
init_info.NumFramesInFlight = m_SwapChainDesc.BufferCount;
init_info.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
init_info.DSVFormat = DXGI_FORMAT_UNKNOWN;
// Allocating SRV descriptors (for textures) is up to the application, so we provide callbacks.
// (current version of the backend will only allocate one descriptor, future versions will need to allocate more)
init_info.SrvDescriptorHeap = m_DescriptorHeap.Get();
init_info.SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_handle) {
return GetInstance().m_HeapAllocator.Alloc(out_cpu_handle, out_gpu_handle);
};
init_info.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle) {
return GetInstance().m_HeapAllocator.Free(cpu_handle, gpu_handle);
};
ImGui_ImplDX12_Init(&init_info);
ImGui::StyleColorsDark();
LOG(INFO) << "DirectX 12 renderer has finished initializing.";
m_Initialized = true;
return true;
}
bool Renderer::InitImpl()
{
while (!*Pointers.Hwnd || !*Pointers.ScreenResX)
{
std::this_thread::sleep_for(1s);
}
LOG(INFO) << "Using DX12, clear shader cache if you're having issues.";
return InitDX12();
}
bool Renderer::AddRendererCallBackImpl(RendererCallBack&& callback, std::uint32_t priority)
{
return m_RendererCallBacks.insert({priority, callback}).second;
}
void Renderer::AddWindowProcedureCallbackImpl(WindowProcedureCallback&& callback)
{
return m_WindowProcedureCallbacks.push_back(callback);
}
void Renderer::DX12OnPresentImpl()
{
if (!m_SafeToRender)
return;
Renderer::DX12NewFrame();
for (const auto& callback : m_RendererCallBacks | std::views::values)
callback();
Renderer::DX12EndFrame();
}
LRESULT Renderer::WndProcImpl(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
for (const auto& callback : m_WindowProcedureCallbacks)
callback(hwnd, msg, wparam, lparam);
return ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam);
}
void Renderer::ResizeImpl(float scale)
{
DX12PreResize();
if (scale != 1.0f)
ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetStyle().MouseCursorScale = 1.0f;
ImGui::GetIO().FontGlobalScale = scale;
DX12PostResize();
}
void Renderer::WaitForLastFrame()
{
FrameContext FrameCtx = GetInstance().m_FrameContext[GetInstance().m_FrameIndex % GetInstance().m_SwapChainDesc.BufferCount];
UINT64 FenceValue = FrameCtx.FenceValue;
if (FenceValue == 0)
{
return;
}
FrameCtx.FenceValue = 0;
if (GetInstance().m_Fence->GetCompletedValue() >= FenceValue)
{
return;
}
GetInstance().m_Fence->SetEventOnCompletion(FenceValue, GetInstance().m_FenceEvent);
WaitForSingleObject(GetInstance().m_FenceEvent, INFINITE);
}
void Renderer::WaitForNextFrame()
{
UINT NextFrameIndex = GetInstance().m_FrameIndex + 1;
GetInstance().m_FrameIndex = NextFrameIndex;
HANDLE WaitableObjects[] = {GetInstance().m_SwapchainWaitableObject, nullptr};
DWORD NumWaitableObjets = 1;
FrameContext FrameCtx = GetInstance().m_FrameContext[NextFrameIndex % GetInstance().m_SwapChainDesc.BufferCount];
UINT64 FenceValue = FrameCtx.FenceValue;
if (FenceValue != 0) // means no fence was signaled
{
FrameCtx.FenceValue = 0;
GetInstance().m_Fence->SetEventOnCompletion(FenceValue, GetInstance().m_FenceEvent);
WaitableObjects[1] = GetInstance().m_FenceEvent;
NumWaitableObjets = 2;
}
WaitForMultipleObjects(NumWaitableObjets, WaitableObjects, TRUE, INFINITE);
}
void Renderer::DX12PreResize()
{
SetResizing(true);
WaitForLastFrame();
ImGui_ImplDX12_InvalidateDeviceObjects();
for (size_t i{}; i != GetInstance().m_SwapChainDesc.BufferCount; ++i)
{
REL(GetInstance().m_FrameContext[i].Resource);
}
}
void Renderer::DX12PostResize()
{
//Recreate our pointers and ImGui's
ImGui_ImplDX12_CreateDeviceObjects();
const auto RTVDescriptorSize{GetInstance().m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV)};
D3D12_CPU_DESCRIPTOR_HANDLE RTVHandle{GetInstance().m_BackbufferDescriptorHeap->GetCPUDescriptorHandleForHeapStart()};
for (size_t i{}; i != GetInstance().m_SwapChainDesc.BufferCount; ++i)
{
ComPtr<ID3D12Resource> BackBuffer{};
GetInstance().m_FrameContext[i].Descriptor = RTVHandle;
GetInstance().m_SwapChain->GetBuffer(i, __uuidof(ID3D12Resource), (void**)BackBuffer.GetAddressOf());
GetInstance().m_Device->CreateRenderTargetView(BackBuffer.Get(), nullptr, RTVHandle);
GetInstance().m_FrameContext[i].Resource = BackBuffer.Get();
RTVHandle.ptr += RTVDescriptorSize;
}
SetResizing(false);
}
void Renderer::DX12NewFrame()
{
if (GetInstance().m_FontsUpdated)
{
DX12PreResize();
DX12PostResize();
GetInstance().m_FontsUpdated = false;
}
ImGui_ImplDX12_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
}
void Renderer::DX12EndFrame()
{
WaitForNextFrame();
FrameContext& CurrentFrameContext{GetInstance().m_FrameContext[GetInstance().m_SwapChain->GetCurrentBackBufferIndex()]};
CurrentFrameContext.CommandAllocator->Reset();
D3D12_RESOURCE_BARRIER Barrier{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{CurrentFrameContext.Resource, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET}}};
GetInstance().m_CommandList->Reset(CurrentFrameContext.CommandAllocator, nullptr);
GetInstance().m_CommandList->ResourceBarrier(1, &Barrier);
GetInstance().m_CommandList->OMSetRenderTargets(1, &CurrentFrameContext.Descriptor, FALSE, nullptr);
GetInstance().m_CommandList->SetDescriptorHeaps(1, GetInstance().m_DescriptorHeap.GetAddressOf());
ImGui::Render();
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), GetInstance().m_CommandList.Get());
Barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
Barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
GetInstance().m_CommandList->ResourceBarrier(1, &Barrier);
GetInstance().m_CommandList->Close();
ID3D12CommandList* CommandLists[]{GetInstance().m_CommandList.Get()};
GetInstance().m_CommandQueue->ExecuteCommandLists(_countof(CommandLists), CommandLists);
UINT64 FenceValue = GetInstance().m_FenceLastSignaledValue + 1;
GetInstance().m_CommandQueue->Signal(GetInstance().m_Fence.Get(), FenceValue);
GetInstance().m_FenceLastSignaledValue = FenceValue;
CurrentFrameContext.FenceValue = FenceValue;
}
if (FAILED(device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_SrvHeap))))
return false;
m_SrvAllocator.Init(device, m_SrvHeap.Get());
auto rtvIncrement = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
auto rtvStart = m_RtvHeap->GetCPUDescriptorHandleForHeapStart();
m_FrameContexts.resize(bufferCount);
m_RtvHandles.resize(bufferCount);
m_BackBuffers.resize(bufferCount);
for (UINT i = 0; i < bufferCount; i++)
{
if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_FrameContexts[i].Allocator))))
return false;
m_RtvHandles[i] = {rtvStart.ptr + static_cast<SIZE_T>(i) * rtvIncrement};
}
if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_FrameContexts[0].Allocator.Get(), nullptr, IID_PPV_ARGS(&m_CommandList))))
return false;
m_CommandList->Close();
if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_Fence))))
return false;
m_FenceEvent = CreateEventA(nullptr, FALSE, FALSE, nullptr);
m_DeviceObjectsValid = true;
return true;
}
void Renderer::CreateRenderTargets(IDXGISwapChain3* swapChain)
{
ComPtr<ID3D12Device> owningDevice{};
swapChain->GetDevice(IID_PPV_ARGS(&owningDevice));
for (UINT i = 0; i < m_BufferCount; ++i)
{
if (FAILED(swapChain->GetBuffer(i, IID_PPV_ARGS(&m_BackBuffers[i]))))
continue;
owningDevice->CreateRenderTargetView(m_BackBuffers[i].Get(), nullptr, m_RtvHandles[i]);
}
}
void Renderer::ReleaseRenderTargets()
{
if (m_Fence && m_FenceEvent)
{
auto value = ++m_FenceValue;
if (SUCCEEDED(m_Fence->Signal(value)) && m_Fence->GetCompletedValue() < value)
{
m_Fence->SetEventOnCompletion(value, m_FenceEvent);
WaitForSingleObject(m_FenceEvent, 2000);
}
}
for (auto& bb : m_BackBuffers)
bb.Reset();
}
LRESULT Renderer::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (GetInstance().m_Initialized)
{
for (const auto& callback : GetInstance().m_WindowProcedureCallbacks)
callback(hwnd, msg, wparam, lparam);
ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam);
}
if (GetInstance().m_OriginalWndProc)
return CallWindowProcA(GetInstance().m_OriginalWndProc, hwnd, msg, wparam, lparam);
return DefWindowProcA(hwnd, msg, wparam, lparam);
}
void Renderer::InvalidateDeviceObjects()
{
if (!m_DeviceObjectsValid)
return;
ReleaseRenderTargets();
if (m_Initialized)
ImGui_ImplDX12_InvalidateDeviceObjects();
m_DeviceObjectsValid = false;
}
void Renderer::SrvHeapAllocator::Init(ID3D12Device* device, ID3D12DescriptorHeap* srvHeap)
{
Heap = srvHeap;
DescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
HeapStartCpu = Heap->GetCPUDescriptorHandleForHeapStart();
HeapStartGpu = Heap->GetGPUDescriptorHandleForHeapStart();
FreeIndices.clear();
FreeIndices.reserve(64);
for (int i = 64 - 1; i >= 0; --i)
FreeIndices.push_back(i);
}
void Renderer::SrvHeapAllocator::Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* outCpu, D3D12_GPU_DESCRIPTOR_HANDLE* outGpu)
{
if (FreeIndices.empty())
{
LOGF(FATAL, "SRV heap exhausted.");
*outCpu = HeapStartCpu;
*outGpu = HeapStartGpu;
return;
}
int index = FreeIndices.back();
FreeIndices.pop_back();
outCpu->ptr = HeapStartCpu.ptr + static_cast<SIZE_T>(index) * DescriptorSize;
outGpu->ptr = HeapStartGpu.ptr + static_cast<UINT64>(index) * DescriptorSize;
}
void Renderer::SrvHeapAllocator::Free(D3D12_CPU_DESCRIPTOR_HANDLE cpu, D3D12_GPU_DESCRIPTOR_HANDLE gpu)
{
int index = static_cast<int>((cpu.ptr - HeapStartCpu.ptr) / DescriptorSize);
FreeIndices.push_back(index);
}
}
+82 -202
View File
@@ -1,159 +1,39 @@
#pragma once
#include "core/util/Joaat.hpp"
#include <backends/imgui_impl_dx12.h>
#include <comdef.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <functional>
#include <map>
#include <windows.h>
#include <wrl/client.h>
#define REL(o) \
o->Release(); \
if (o) \
{ \
o = nullptr; \
}
namespace YimMenu
{
using namespace Microsoft::WRL;
using RendererCallBack = std::function<void()>;
using WindowProcedureCallback = std::function<void(HWND, UINT, WPARAM, LPARAM)>;
class Renderer
{
public:
using RendererCallback = std::function<void()>;
using WindowProcedureCallback = std::function<void(HWND, UINT, WPARAM, LPARAM)>;
struct FrameContext
{
ID3D12CommandAllocator* CommandAllocator;
ID3D12Resource* Resource;
D3D12_CPU_DESCRIPTOR_HANDLE Descriptor;
UINT64 FenceValue;
};
static void Init()
{
GetInstance().InitImpl();
}
struct ExampleDescriptorHeapAllocator
{
ID3D12DescriptorHeap* Heap = nullptr;
D3D12_DESCRIPTOR_HEAP_TYPE HeapType = D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES;
D3D12_CPU_DESCRIPTOR_HANDLE HeapStartCpu;
D3D12_GPU_DESCRIPTOR_HANDLE HeapStartGpu;
UINT HeapHandleIncrement;
ImVector<int> FreeIndices;
static void Destroy()
{
GetInstance().DestroyImpl();
}
void Create(ID3D12Device* device, ID3D12DescriptorHeap* heap)
static void AddRendererCallback(RendererCallback&& callback, uint32_t priority)
{
GetInstance().m_RendererCallbacks.insert({priority, callback});
}
static void AddWindowProcedureCallback(WindowProcedureCallback&& callback)
{
GetInstance().m_WindowProcedureCallbacks.push_back(callback);
}
static bool IsInitialized()
{
IM_ASSERT(Heap == nullptr && FreeIndices.empty());
Heap = heap;
D3D12_DESCRIPTOR_HEAP_DESC desc = heap->GetDesc();
HeapType = desc.Type;
HeapStartCpu = Heap->GetCPUDescriptorHandleForHeapStart();
HeapStartGpu = Heap->GetGPUDescriptorHandleForHeapStart();
HeapHandleIncrement = device->GetDescriptorHandleIncrementSize(HeapType);
FreeIndices.reserve((int)desc.NumDescriptors);
for (int n = desc.NumDescriptors; n > 0; n--)
FreeIndices.push_back(n - 1);
}
void Destroy()
{
Heap = nullptr;
FreeIndices.clear();
}
void Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_desc_handle)
{
IM_ASSERT(FreeIndices.Size > 0);
int idx = FreeIndices.back();
FreeIndices.pop_back();
out_cpu_desc_handle->ptr = HeapStartCpu.ptr + (idx * HeapHandleIncrement);
out_gpu_desc_handle->ptr = HeapStartGpu.ptr + (idx * HeapHandleIncrement);
}
void Free(D3D12_CPU_DESCRIPTOR_HANDLE out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE out_gpu_desc_handle)
{
int cpu_idx = (int)((out_cpu_desc_handle.ptr - HeapStartCpu.ptr) / HeapHandleIncrement);
int gpu_idx = (int)((out_gpu_desc_handle.ptr - HeapStartGpu.ptr) / HeapHandleIncrement);
IM_ASSERT(cpu_idx == gpu_idx);
FreeIndices.push_back(cpu_idx);
}
};
class Renderer final
{
private:
Renderer();
public:
~Renderer();
Renderer(const Renderer&) = delete;
Renderer(Renderer&&) noexcept = delete;
Renderer& operator=(const Renderer&) = delete;
Renderer& operator=(Renderer&&) noexcept = delete;
static void Destroy()
{
GetInstance().DestroyImpl();
}
static bool Init()
{
return GetInstance().InitImpl();
}
/**
* @brief Add a callback function to draw using ImGui
*
* @param callback Callback function
* @param priority Low values will be drawn before higher values.
* @return true Successfully added callback.
* @return false Duplicate render priority was given.
*/
static bool AddRendererCallBack(RendererCallBack&& callback, std::uint32_t priority)
{
return GetInstance().AddRendererCallBackImpl(std::move(callback), priority);
}
/**
* @brief Add a callback function to handle Windows WindowProcedure
*
* @param callback Callback function
*/
static void AddWindowProcedureCallback(WindowProcedureCallback&& callback)
{
GetInstance().AddWindowProcedureCallbackImpl(std::move(callback));
}
static void DX12OnPresent()
{
GetInstance().DX12OnPresentImpl();
}
static LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
return GetInstance().WndProcImpl(hwnd, msg, wparam, lparam);
}
static void WaitForLastFrame();
static void WaitForNextFrame();
static void DX12PreResize();
static void DX12PostResize();
static bool IsResizing()
{
return GetInstance().m_Resizing;
}
static void SetResizing(const bool& status)
{
GetInstance().m_Resizing = status;
}
static void Resize(float scale)
{
GetInstance().ResizeImpl(scale);
}
static void SetFontsUpdated()
{
GetInstance().m_FontsUpdated = true;
return GetInstance().m_Initialized;
}
static void SetSafeToRender()
@@ -161,70 +41,70 @@ namespace YimMenu
GetInstance().m_SafeToRender = true;
}
private:
static void DX12NewFrame();
static void DX12EndFrame();
private:
void DestroyImpl();
bool InitImpl();
bool InitDX12();
void ResizeImpl(float scale);
bool AddRendererCallBackImpl(RendererCallBack&& callback, std::uint32_t priority);
void AddWindowProcedureCallbackImpl(WindowProcedureCallback&& callback);
void DX12OnPresentImpl();
LRESULT WndProcImpl(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static Renderer& GetInstance()
static void SetFontsUpdated()
{
static Renderer i{};
return i;
GetInstance().m_FontsUpdated = true;
}
public:
ExampleDescriptorHeapAllocator m_HeapAllocator;
private:
template <typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
private:
bool m_Initialized;
bool m_Resizing;
bool m_SafeToRender;
struct SrvHeapAllocator
{
void Init(ID3D12Device* device, ID3D12DescriptorHeap* heap);
void Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* outCpu, D3D12_GPU_DESCRIPTOR_HANDLE* outGpu);
void Free(D3D12_CPU_DESCRIPTOR_HANDLE cpu, D3D12_GPU_DESCRIPTOR_HANDLE gpu);
// Shared
ImFontAtlas m_FontAtlas;
ID3D12DescriptorHeap* Heap = nullptr;
UINT DescriptorSize = 0;
D3D12_CPU_DESCRIPTOR_HANDLE HeapStartCpu = {};
D3D12_GPU_DESCRIPTOR_HANDLE HeapStartGpu = {};
std::vector<int> FreeIndices = {};
};
//DX12
std::vector<FrameContext> m_FrameContext;
struct FrameContext
{
ComPtr<ID3D12CommandAllocator> Allocator = {};
UINT64 FenceValue = 0;
};
DXGI_SWAP_CHAIN_DESC m_SwapChainDesc;
ComPtr<IDXGISwapChain1> m_GameSwapChain;
ComPtr<IDXGISwapChain3> m_SwapChain;
ComPtr<ID3D12Device> m_Device;
ComPtr<ID3D12CommandQueue> m_CommandQueue;
ComPtr<ID3D12CommandAllocator> m_CommandAllocator;
ComPtr<ID3D12GraphicsCommandList> m_CommandList;
ComPtr<ID3D12DescriptorHeap> m_BackbufferDescriptorHeap;
ComPtr<ID3D12DescriptorHeap> m_DescriptorHeap;
static Renderer& GetInstance()
{
static Renderer instance;
return instance;
}
ComPtr<ID3D12Fence> m_Fence;
HANDLE m_FenceEvent{};
UINT64 m_FenceLastSignaledValue{};
HANDLE m_SwapchainWaitableObject{};
UINT64 m_FrameIndex{};
void InitImpl();
void DestroyImpl();
bool InitInternal();
void Render();
void Resize();
bool CreateDeviceObjects(ID3D12Device* device, ID3D12CommandQueue* queue, UINT bufferCount);
void CreateRenderTargets(IDXGISwapChain3* swapChain);
void ReleaseRenderTargets();
void InvalidateDeviceObjects();
bool m_FontsUpdated;
static LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
private:
//Other
std::map<joaat_t, RendererCallBack> m_RendererCallBacks;
std::vector<WindowProcedureCallback> m_WindowProcedureCallbacks;
};
}
// Make our linker aware of the ImGui WndProcHandler
extern LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
bool m_Initialized = false;
bool m_DeviceObjectsValid = false;
bool m_SafeToRender = false;
bool m_FontsUpdated = false;
HWND m_Window = nullptr;
WNDPROC m_OriginalWndProc = nullptr;
ComPtr<ID3D12DescriptorHeap> m_RtvHeap = {};
ComPtr<ID3D12DescriptorHeap> m_SrvHeap = {};
SrvHeapAllocator m_SrvAllocator = {};
std::vector<FrameContext> m_FrameContexts = {};
std::vector<ComPtr<ID3D12Resource>> m_BackBuffers = {};
std::vector<D3D12_CPU_DESCRIPTOR_HANDLE> m_RtvHandles = {};
UINT m_BufferCount = 0;
ComPtr<ID3D12GraphicsCommandList> m_CommandList = {};
ComPtr<ID3D12Fence> m_Fence = {};
HANDLE m_FenceEvent = nullptr;
UINT64 m_FenceValue = 0;
std::map<uint32_t, RendererCallback> m_RendererCallbacks;
std::vector<WindowProcedureCallback> m_WindowProcedureCallbacks;
};
}
+8 -16
View File
@@ -11,49 +11,45 @@
#include "game/frontend/ChatDisplay.hpp"
#include "game/gta/Natives.hpp"
#include "types/pad/ControllerInputs.hpp"
#include "core/frontend/manager/styles/Themes.hpp"
namespace YimMenu
{
GUI::GUI() :
m_IsOpen(false)
void GUI::InitImpl()
{
Menu::SetupFonts();
SetupStyle();
Menu::Init();
Renderer::AddWindowProcedureCallback([this](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
GUI::WndProc(hwnd, msg, wparam, lparam);
});
Renderer::AddRendererCallBack(
Renderer::AddRendererCallback(
[&] {
Notifications::Draw();
},
-2);
Renderer::AddRendererCallBack(
Renderer::AddRendererCallback(
[&] {
ESP::Draw();
},
-3);
Renderer::AddRendererCallBack(
Renderer::AddRendererCallback(
[&] {
ChatDisplay::Draw();
},
-5);
Renderer::AddRendererCallBack(
Renderer::AddRendererCallback(
[&] {
Overlay::Draw();
},
-6);
Renderer::AddRendererCallBack(
Renderer::AddRendererCallback(
[] {
LuaManager::ForAllLoadedScripts([](std::shared_ptr<LuaScript>& script) {
script->GetUserInterface().DrawAlwaysDrawImGuiCallbacks();
});
},
-7);
Renderer::AddRendererCallBack(
Renderer::AddRendererCallback(
[] {
if (!GUI::IsOpen())
return;
@@ -66,10 +62,6 @@ namespace YimMenu
Renderer::SetSafeToRender();
}
GUI::~GUI()
{
}
void GUI::ToggleMouse()
{
auto& io = ImGui::GetIO();
@@ -110,7 +102,7 @@ namespace YimMenu
{
while (g_Running)
{
if (GUI::IsOpen())
if (Renderer::IsInitialized() && GUI::IsOpen())
{
if (UIManager::ShowingContentWindow())
{
+4 -14
View File
@@ -1,24 +1,13 @@
#pragma once
#include <windows.h>
namespace YimMenu
{
class GUI final
{
private:
GUI();
public:
~GUI();
GUI(const GUI&) = delete;
GUI(GUI&&) noexcept = delete;
GUI& operator=(const GUI&) = delete;
GUI& operator=(GUI&&) noexcept = delete;
static void Init()
{
GetInstance().m_IsOpen = false;
GetInstance().InitImpl();
}
static bool IsOpen()
@@ -51,13 +40,15 @@ namespace YimMenu
return GetInstance().m_Onboarding;
}
static void ToggleMouse();
private:
void WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
void InitImpl();
void SetOnboardingImpl(bool state);
void RunScriptImpl();
static void ToggleMouse();
static GUI& GetInstance()
{
static GUI i{};
@@ -65,7 +56,6 @@ namespace YimMenu
return i;
}
private:
bool m_IsOpen;
bool m_Onboarding;
};
+1 -11
View File
@@ -1,13 +1,7 @@
#include "Menu.hpp"
#include "imgui.h"
#include "imgui_internal.h"
#include "core/commands/Commands.hpp"
#include "core/frontend/manager/UIManager.hpp"
#include "core/renderer/Renderer.hpp"
#include "core/backend/FiberPool.hpp"
#include "core/backend/ScriptMgr.hpp"
#include "game/frontend/fonts/Fonts.hpp"
#include "game/pointers/Pointers.hpp"
#include "submenus/Self.hpp"
#include "submenus/Vehicle.hpp"
#include "submenus/Teleport.hpp"
@@ -17,16 +11,12 @@
#include "submenus/Settings.hpp"
#include "submenus/Debug.hpp"
#include "submenus/World.hpp"
#include "core/filemgr/FileMgr.hpp"
#include "core/memory/ModuleMgr.hpp"
#include "Onboarding.hpp"
#include "core/frontend/manager/styles/Themes.hpp"
namespace YimMenu
{
void Menu::Init()
{
SetupStyle();
// Arguably the only place this file should be edited at for more menus
UIManager::AddSubmenu(std::make_shared<Submenus::Self>());
UIManager::AddSubmenu(std::make_shared<Submenus::Vehicle>());
@@ -38,7 +28,7 @@ namespace YimMenu
UIManager::AddSubmenu(std::make_shared<Submenus::Settings>());
UIManager::AddSubmenu(std::make_shared<Submenus::Debug>());
Renderer::AddRendererCallBack(
Renderer::AddRendererCallback(
[&] {
ProcessOnboarding();
if (!GUI::IsOpen())
-1
View File
@@ -1,5 +1,4 @@
#pragma once
#include "game/frontend/GUI.hpp"
namespace YimMenu::Menu
{
+10 -9
View File
@@ -1,25 +1,26 @@
#include "core/hooking/DetourHook.hpp"
#include "game/frontend/GUI.hpp"
#include "core/renderer/Renderer.hpp"
#include "game/hooks/Hooks.hpp"
#include <imgui.h>
namespace YimMenu::Hooks
{
UINT RawInput::GetRawInputData(HRAWINPUT hRawInput, UINT uiCommand, LPVOID pData, PUINT pcbSize, UINT cbSizeHeader)
{
auto result = BaseHook::Get<RawInput::GetRawInputData, DetourHook<decltype(&RawInput::GetRawInputData)>>()->Original()(hRawInput, uiCommand, pData, pcbSize, cbSizeHeader);
if (result > 0 && pData && uiCommand == RID_INPUT && ImGui::GetIO().WantCaptureMouse)
if (result > 0 && pData && uiCommand == RID_INPUT)
{
auto& raw = *(RAWINPUT*)pData;
if (raw.header.dwType == RIM_TYPEMOUSE && raw.data.mouse.usButtonFlags)
if (Renderer::IsInitialized() && ImGui::GetIO().WantCaptureMouse)
{
// Zero out button flags to prevent game from seeing clicks while menu is open
raw.data.mouse.usButtonFlags = 0;
auto& raw = *(RAWINPUT*)pData;
if (raw.header.dwType == RIM_TYPEMOUSE && raw.data.mouse.usButtonFlags)
{
// Zero out button flags to prevent game from seeing clicks while menu is open
raw.data.mouse.usButtonFlags = 0;
}
}
}
return result;
}
}
}
-28
View File
@@ -1,28 +0,0 @@
#include "core/hooking/DetourHook.hpp"
#include "core/renderer/Renderer.hpp"
#include "game/hooks/Hooks.hpp"
namespace YimMenu::Hooks
{
HRESULT SwapChain::Present(IDXGISwapChain* that, UINT syncInterval, UINT flags)
{
if (g_Running && !Renderer::IsResizing())
{
Renderer::DX12OnPresent();
}
return BaseHook::Get<SwapChain::Present, DetourHook<decltype(&SwapChain::Present)>>()->Original()(that, syncInterval, flags);
}
HRESULT SwapChain::ResizeBuffers(IDXGISwapChain* that, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags)
{
if (g_Running)
{
Renderer::DX12PreResize();
const auto result =
BaseHook::Get<SwapChain::ResizeBuffers, DetourHook<decltype(&SwapChain::ResizeBuffers)>>()->Original()(that, bufferCount, width, height, newFormat, swapChainFlags);
Renderer::DX12PostResize();
return result;
}
return BaseHook::Get<SwapChain::ResizeBuffers, DetourHook<decltype(&SwapChain::ResizeBuffers)>>()->Original()(that, bufferCount, width, height, newFormat, swapChainFlags);
}
}
-14
View File
@@ -1,14 +0,0 @@
#include "core/hooking/DetourHook.hpp"
#include "core/renderer/Renderer.hpp"
#include "game/hooks/Hooks.hpp"
namespace YimMenu::Hooks
{
LRESULT Window::WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
if (g_Running)
Renderer::WndProc(hwnd, umsg, wparam, lparam);
return BaseHook::Get<Window::WndProc, DetourHook<WNDPROC>>()->Original()(hwnd, umsg, wparam, lparam);
}
}
-9
View File
@@ -1,5 +1,4 @@
#pragma once
#include <d3d11.h>
class CNetworkPlayerMgr;
class CNetGamePlayer;
@@ -34,14 +33,6 @@ namespace YimMenu
namespace YimMenu::Hooks
{
namespace SwapChain
{
constexpr auto VMTPresentIdx = 8;
constexpr auto VMTResizeBuffersIdx = 13;
extern HRESULT Present(IDXGISwapChain* that, UINT syncInterval, UINT flags);
extern HRESULT ResizeBuffers(IDXGISwapChain* that, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags);
}
namespace Anticheat
{
extern void QueueDependency(__int64 a1);
-16
View File
@@ -28,22 +28,6 @@ namespace YimMenu
auto scanner = PatternScanner(gta5);
constexpr auto swapchainPtrn = Pattern<"72 C7 EB 02 31 C0 8B 0D">("IDXGISwapChain");
scanner.Add(swapchainPtrn, [this](PointerCalculator ptr) {
CommandQueue = ptr.Add(0x1A).Add(3).Rip().As<ID3D12CommandQueue**>();
SwapChain = ptr.Add(0x21).Add(3).Rip().As<IDXGISwapChain1**>();
});
constexpr auto wndProcPtrn = Pattern<"3D 85 00 00 00 0F 87 2D 02 00 00">("WndProc");
scanner.Add(wndProcPtrn, [this](PointerCalculator ptr) {
WndProc = ptr.Sub(0x4F).As<PVOID>();
});
constexpr auto hWndPtrn = Pattern<"E8 ? ? ? ? 84 C0 74 25 48 8B 0D">("HWND");
scanner.Add(hWndPtrn, [this](PointerCalculator ptr) {
Hwnd = ptr.Add(9).Add(3).Rip().As<HWND*>();
});
constexpr auto screenResPtrn = Pattern<"75 39 0F 57 C0 F3 0F 2A 05">("ScreenRes");
scanner.Add(screenResPtrn, [this](PointerCalculator ptr) {
ScreenResX = ptr.Add(0x5).Add(4).Rip().As<std::uint32_t*>();
-7
View File
@@ -1,7 +1,4 @@
#pragma once
#include <d3d12.h>
#include <dxgi1_4.h>
#include <windows.h>
#include "types/script/scrNativeHandler.hpp"
#include "types/rage/ObfVar.hpp"
#include "core/memory/BytePatches.hpp"
@@ -74,10 +71,6 @@ namespace YimMenu
struct PointerData
{
IDXGISwapChain1** SwapChain;
ID3D12CommandQueue** CommandQueue;
HWND* Hwnd;
PVOID WndProc;
std::uint32_t* ScreenResX;
std::uint32_t* ScreenResY;
const char* GameVersion;
+7 -3
View File
@@ -9,6 +9,7 @@
#include "core/hooking/Hooking.hpp"
#include "core/hooking/CallHook.hpp"
#include "core/memory/ModuleMgr.hpp"
#include "core/renderer/D3D12Hook.hpp"
#include "core/renderer/Renderer.hpp"
#include "core/util/Wine.hpp"
#include "core/scripting/LuaManager.hpp"
@@ -52,9 +53,6 @@ namespace YimMenu
AnticheatBypass::RunOnStartup();
if (!Renderer::Init())
goto EARLY_UNLOAD;
Players::Init();
Hooking::Init();
@@ -64,6 +62,11 @@ namespace YimMenu
ScriptPointers::Init();
if (!D3D12Hook::Init())
goto EARLY_UNLOAD;
Renderer::Init();
while (!Renderer::IsInitialized())
std::this_thread::sleep_for(100ms);
GUI::Init();
ScriptMgr::AddScript(std::make_unique<Script>(&NativeHooks::RunScript)); // runs once
@@ -103,6 +106,7 @@ namespace YimMenu
EARLY_UNLOAD:
g_Running = false;
D3D12Hook::Destroy(true);
Renderer::Destroy();
LogHelper::Destroy();