Add IInternal/PublicDisposableService (#1696)

* Add IInternal/PublicDisposableService

Plugins are exposed interfaces that are not inherited from
`IDisposable`, but services implementing plugin interfaces often
implement `IDisposable`. Some plugins may try to call
`IDisposable.Dispose` on everything provided, and it also is possible to
use `using` clause too eagerly while working on Dalamud itself, such as
writing `using var smth = await Service<SomeService>.GetAsync();`. Such
behaviors often lead to a difficult-to-debug errors, and making those
services either not an `IDisposable` or making `IDisposable.Dispose` do
nothing if the object has been loaded would prevent such errors. As
`ServiceManager` must be the only class dealing with construction and
disposal of services, `IInternalDisposableService` has been added to
limit who can dispose the object. `IPublicDisposableService` also has
been added to classes that can be constructed and accessed directly by
plugins; for those, `Dispose` will be ignored if the instance is a
service instance, and only `DisposeService` will respond.

In addition, `DalamudPluginInterface` and `UiBuilder` also have been
changed so that their `IDisposable.Dispose` no longer respond, and
instead, internal functions have been added to only allow disposal from
Dalamud.

* Cleanup

* Postmerge fixes

* More explanation on RunOnFrameworkThread(ClearHooks)

* Mark ReliableFileStorage public ctor obsolete

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
This commit is contained in:
srkizer 2024-03-17 00:58:05 +09:00 committed by GitHub
parent dcec076ca7
commit 87b9edb448
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 441 additions and 381 deletions

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.Internal;
/// This class disables anti-debug functionality in the game client.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal sealed partial class AntiDebug : IServiceType
internal sealed class AntiDebug : IInternalDisposableService
{
private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
private byte[] original;
@ -43,16 +43,25 @@ internal sealed partial class AntiDebug : IServiceType
}
}
/// <summary>Finalizes an instance of the <see cref="AntiDebug"/> class.</summary>
~AntiDebug() => this.Disable();
/// <summary>
/// Gets a value indicating whether the anti-debugging is enabled.
/// </summary>
public bool IsEnabled { get; private set; } = false;
/// <inheritdoc />
void IInternalDisposableService.DisposeService() => this.Disable();
/// <summary>
/// Enables the anti-debugging by overwriting code in memory.
/// </summary>
public void Enable()
{
if (this.IsEnabled)
return;
this.original = new byte[this.nop.Length];
if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled)
{
@ -73,6 +82,9 @@ internal sealed partial class AntiDebug : IServiceType
/// </summary>
public void Disable()
{
if (!this.IsEnabled)
return;
if (this.debugCheckAddress != IntPtr.Zero && this.original != null)
{
Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}");
@ -86,45 +98,3 @@ internal sealed partial class AntiDebug : IServiceType
this.IsEnabled = false;
}
}
/// <summary>
/// Implementing IDisposable.
/// </summary>
internal sealed partial class AntiDebug : IDisposable
{
private bool disposed = false;
/// <summary>
/// Finalizes an instance of the <see cref="AntiDebug"/> class.
/// </summary>
~AntiDebug() => this.Dispose(false);
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
/// <param name="disposing">If this was disposed through calling Dispose() or from being finalized.</param>
private void Dispose(bool disposing)
{
if (this.disposed)
return;
if (disposing)
{
// If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded.
// If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the
// check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it.
this.Disable();
}
this.disposed = true;
}
}

View file

@ -20,7 +20,7 @@ namespace Dalamud.Game.Internal;
/// This class implements in-game Dalamud options in the in-game System menu.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
{
private readonly AtkValueChangeType atkValueChangeType;
private readonly AtkValueSetString atkValueSetString;
@ -40,6 +40,8 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
private readonly string locDalamudPlugins;
private readonly string locDalamudSettings;
private bool disposed = false;
[ServiceManager.ServiceConstructor]
private DalamudAtkTweaks(TargetSigScanner sigScanner)
{
@ -69,6 +71,9 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
}
/// <summary>Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.</summary>
~DalamudAtkTweaks() => this.Dispose(false);
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type);
@ -79,6 +84,26 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
/// <inheritdoc/>
void IInternalDisposableService.DisposeService() => this.Dispose(true);
private void Dispose(bool disposing)
{
if (this.disposed)
return;
if (disposing)
{
this.hookAgentHudOpenSystemMenu.Dispose();
this.hookUiModuleRequestMainCommand.Dispose();
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
// this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
}
this.disposed = true;
}
/*
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
{
@ -229,45 +254,3 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
}
}
}
/// <summary>
/// Implements IDisposable.
/// </summary>
internal sealed partial class DalamudAtkTweaks : IDisposable
{
private bool disposed = false;
/// <summary>
/// Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.
/// </summary>
~DalamudAtkTweaks() => this.Dispose(false);
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
private void Dispose(bool disposing)
{
if (this.disposed)
return;
if (disposing)
{
this.hookAgentHudOpenSystemMenu.Dispose();
this.hookUiModuleRequestMainCommand.Dispose();
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
// this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
}
this.disposed = true;
}
}