mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 20:54:16 +01:00
Merge branch 'master' of https://github.com/goatcorp/Dalamud
This commit is contained in:
commit
1c593f8350
5 changed files with 224 additions and 14 deletions
|
|
@ -1,11 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Game.Internal.Gui;
|
using Dalamud.Game.Internal.Gui;
|
||||||
using Dalamud.Game.Internal.Libc;
|
using Dalamud.Game.Internal.Libc;
|
||||||
using Dalamud.Game.Internal.Network;
|
using Dalamud.Game.Internal.Network;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Dalamud.Game.Internal.File;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal {
|
namespace Dalamud.Game.Internal {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -30,6 +32,11 @@ namespace Dalamud.Game.Internal {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FrameworkAddressResolver Address { get; }
|
public FrameworkAddressResolver Address { get; }
|
||||||
|
|
||||||
|
#region Stats
|
||||||
|
public static bool StatsEnabled { get; set; }
|
||||||
|
public static Dictionary<string, List<double>> StatsHistory = new Dictionary<string, List<double>>();
|
||||||
|
private static Stopwatch statsStopwatch = new Stopwatch();
|
||||||
|
#endregion
|
||||||
#region Subsystems
|
#region Subsystems
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -108,7 +115,35 @@ namespace Dalamud.Game.Internal {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OnUpdateEvent?.Invoke(this);
|
if (StatsEnabled && OnUpdateEvent != null) {
|
||||||
|
// Stat Tracking for Framework Updates
|
||||||
|
var invokeList = OnUpdateEvent.GetInvocationList();
|
||||||
|
var notUpdated = StatsHistory.Keys.ToList();
|
||||||
|
// Individually invoke OnUpdate handlers and time them.
|
||||||
|
foreach (var d in invokeList) {
|
||||||
|
statsStopwatch.Restart();
|
||||||
|
d.Method.Invoke(d.Target, new object[]{ this });
|
||||||
|
statsStopwatch.Stop();
|
||||||
|
var key = $"{d.Target}::{d.Method.Name}";
|
||||||
|
if (notUpdated.Contains(key)) notUpdated.Remove(key);
|
||||||
|
if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List<double>());
|
||||||
|
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
||||||
|
if (StatsHistory[key].Count > 1000) {
|
||||||
|
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup handlers that are no longer being called
|
||||||
|
foreach (var key in notUpdated) {
|
||||||
|
if (StatsHistory[key].Count > 0) {
|
||||||
|
StatsHistory[key].RemoveAt(0);
|
||||||
|
} else {
|
||||||
|
StatsHistory.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OnUpdateEvent?.Invoke(this);
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using EasyHook;
|
using EasyHook;
|
||||||
|
|
@ -9,9 +10,7 @@ namespace Dalamud.Hooking {
|
||||||
/// This class is basically a thin wrapper around the LocalHook type to provide helper functions.
|
/// This class is basically a thin wrapper around the LocalHook type to provide helper functions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
||||||
public sealed class Hook<T> : IDisposable where T : Delegate {
|
public sealed class Hook<T> : IDisposable, IDalamudHook where T : Delegate {
|
||||||
private bool isDisposed;
|
|
||||||
|
|
||||||
private readonly IntPtr address;
|
private readonly IntPtr address;
|
||||||
|
|
||||||
private readonly T original;
|
private readonly T original;
|
||||||
|
|
@ -69,19 +68,20 @@ namespace Dalamud.Hooking {
|
||||||
this.hookInfo = LocalHook.Create(address, detour, callbackParam); // Installs a hook here
|
this.hookInfo = LocalHook.Create(address, detour, callbackParam); // Installs a hook here
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.original = Marshal.GetDelegateForFunctionPointer<T>(this.hookInfo.HookBypassAddress);
|
this.original = Marshal.GetDelegateForFunctionPointer<T>(this.hookInfo.HookBypassAddress);
|
||||||
|
HookInfo.TrackedHooks.Add(new HookInfo() { Delegate = detour, Hook = this, Assembly = Assembly.GetCallingAssembly()});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a hook from the current process.
|
/// Remove a hook from the current process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
if (this.isDisposed) {
|
if (this.IsDisposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hookInfo.Dispose();
|
this.hookInfo.Dispose();
|
||||||
|
|
||||||
this.isDisposed = true;
|
this.IsDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -104,9 +104,24 @@ namespace Dalamud.Hooking {
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void CheckDisposed() {
|
private void CheckDisposed() {
|
||||||
if (this.isDisposed) {
|
if (this.IsDisposed) {
|
||||||
throw new ObjectDisposedException("Hook is already disposed.");
|
throw new ObjectDisposedException("Hook is already disposed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the hook is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled {
|
||||||
|
get {
|
||||||
|
CheckDisposed();
|
||||||
|
return this.hookInfo.ThreadACL.IsExclusive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the hook has been disposed
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
Dalamud/Hooking/HookInfo.cs
Normal file
36
Dalamud/Hooking/HookInfo.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking {
|
||||||
|
internal class HookInfo {
|
||||||
|
|
||||||
|
internal static List<HookInfo> TrackedHooks = new List<HookInfo>();
|
||||||
|
|
||||||
|
internal IDalamudHook Hook { get; set; }
|
||||||
|
internal Delegate Delegate { get; set; }
|
||||||
|
internal Assembly Assembly { get; set; }
|
||||||
|
|
||||||
|
private ulong? inProcessMemory = 0;
|
||||||
|
internal ulong? InProcessMemory {
|
||||||
|
get {
|
||||||
|
if (Hook.IsDisposed) return 0;
|
||||||
|
if (this.inProcessMemory == null) return null;
|
||||||
|
if (this.inProcessMemory.Value > 0) return this.inProcessMemory.Value;
|
||||||
|
var p = Process.GetCurrentProcess().MainModule;
|
||||||
|
var begin = (ulong) p.BaseAddress.ToInt64();
|
||||||
|
var end = begin + (ulong) p.ModuleMemorySize;
|
||||||
|
var hookAddr = (ulong) Hook.Address.ToInt64();
|
||||||
|
if (hookAddr >= begin && hookAddr <= end) {
|
||||||
|
this.inProcessMemory = hookAddr - begin;
|
||||||
|
return this.inProcessMemory.Value;
|
||||||
|
} else {
|
||||||
|
this.inProcessMemory = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Dalamud/Hooking/IDalamudHook.cs
Normal file
9
Dalamud/Hooking/IDalamudHook.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking {
|
||||||
|
internal interface IDalamudHook {
|
||||||
|
public IntPtr Address { get; }
|
||||||
|
public bool IsEnabled { get; }
|
||||||
|
public bool IsDisposed { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Dalamud.Game.Internal;
|
||||||
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Dalamud.Interface {
|
namespace Dalamud.Interface {
|
||||||
class DalamudPluginStatWindow : IDisposable {
|
internal class DalamudPluginStatWindow : IDisposable {
|
||||||
|
|
||||||
private PluginManager pm;
|
private readonly PluginManager pluginManager;
|
||||||
public DalamudPluginStatWindow(PluginManager pm) {
|
private bool showDalamudHooks;
|
||||||
this.pm = pm;
|
|
||||||
|
public DalamudPluginStatWindow(PluginManager pluginManager) {
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Draw() {
|
public bool Draw() {
|
||||||
|
|
@ -29,7 +34,7 @@ namespace Dalamud.Interface {
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Reset")) {
|
if (ImGui.Button("Reset")) {
|
||||||
foreach (var a in this.pm.Plugins) {
|
foreach (var a in this.pluginManager.Plugins) {
|
||||||
a.PluginInterface.UiBuilder.lastDrawTime = -1;
|
a.PluginInterface.UiBuilder.lastDrawTime = -1;
|
||||||
a.PluginInterface.UiBuilder.maxDrawTime = -1;
|
a.PluginInterface.UiBuilder.maxDrawTime = -1;
|
||||||
a.PluginInterface.UiBuilder.drawTimeHistory.Clear();
|
a.PluginInterface.UiBuilder.drawTimeHistory.Clear();
|
||||||
|
|
@ -51,7 +56,7 @@ namespace Dalamud.Interface {
|
||||||
ImGui.Text("Average");
|
ImGui.Text("Average");
|
||||||
ImGui.NextColumn();
|
ImGui.NextColumn();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
foreach (var a in this.pm.Plugins) {
|
foreach (var a in this.pluginManager.Plugins) {
|
||||||
ImGui.Text(a.Definition.Name);
|
ImGui.Text(a.Definition.Name);
|
||||||
ImGui.NextColumn();
|
ImGui.NextColumn();
|
||||||
ImGui.Text($"{a.PluginInterface.UiBuilder.lastDrawTime/10000f:F4}ms");
|
ImGui.Text($"{a.PluginInterface.UiBuilder.lastDrawTime/10000f:F4}ms");
|
||||||
|
|
@ -69,7 +74,117 @@ namespace Dalamud.Interface {
|
||||||
ImGui.Columns(1);
|
ImGui.Columns(1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Framework times")) {
|
||||||
|
|
||||||
|
var doStats = Framework.StatsEnabled;
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) {
|
||||||
|
Framework.StatsEnabled = doStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Reset")) {
|
||||||
|
Framework.StatsHistory.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Columns(4);
|
||||||
|
ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300);
|
||||||
|
ImGui.SetColumnWidth(1, 100f);
|
||||||
|
ImGui.SetColumnWidth(2, 100f);
|
||||||
|
ImGui.SetColumnWidth(3, 100f);
|
||||||
|
ImGui.Text("Method");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text("Last");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text("Longest");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text("Average");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
foreach (var handlerHistory in Framework.StatsHistory) {
|
||||||
|
if (handlerHistory.Value.Count == 0) continue;
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text($"{handlerHistory.Key}");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text($"{handlerHistory.Value.Last():F4}ms");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text($"{handlerHistory.Value.Max():F4}ms");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text($"{handlerHistory.Value.Average():F4}ms");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
ImGui.Columns(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Hooks")) {
|
||||||
|
ImGui.Columns(3);
|
||||||
|
ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 280);
|
||||||
|
ImGui.SetColumnWidth(1, 180f);
|
||||||
|
ImGui.SetColumnWidth(2, 100f);
|
||||||
|
ImGui.Text("Detour Method");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(" ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref showDalamudHooks);
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text("Address");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Text("Status");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
foreach (var trackedHook in HookInfo.TrackedHooks) {
|
||||||
|
try {
|
||||||
|
if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) continue;
|
||||||
|
|
||||||
|
ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}");
|
||||||
|
ImGui.TextDisabled(trackedHook.Assembly.FullName);
|
||||||
|
ImGui.NextColumn();
|
||||||
|
if (!trackedHook.Hook.IsDisposed) {
|
||||||
|
ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}");
|
||||||
|
if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}");
|
||||||
|
|
||||||
|
var processMemoryOffset = trackedHook.InProcessMemory;
|
||||||
|
if (processMemoryOffset.HasValue) {
|
||||||
|
ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}");
|
||||||
|
if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
if (trackedHook.Hook.IsDisposed) {
|
||||||
|
ImGui.Text("Disposed");
|
||||||
|
} else {
|
||||||
|
ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.NextColumn();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ImGui.Text(ex.Message);
|
||||||
|
ImGui.NextColumn();
|
||||||
|
while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Columns();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsWindowAppearing()) {
|
||||||
|
HookInfo.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
ImGui.EndTabBar();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue