using System; using System.Collections.Generic; #if !DEBUG using Dalamud.Configuration.Internal; #endif using Serilog; namespace Dalamud.Game.Internal; /// /// This class disables anti-debug functionality in the game client. /// [ServiceManager.EarlyLoadedService] internal sealed partial class AntiDebug : IServiceType { private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; private byte[] original; private IntPtr debugCheckAddress; [ServiceManager.ServiceConstructor] private AntiDebug(TargetSigScanner sigScanner) { try { this.debugCheckAddress = sigScanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); } catch (KeyNotFoundException) { this.debugCheckAddress = IntPtr.Zero; } Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); if (!this.IsEnabled) { #if DEBUG this.Enable(); #else if (Service.Get().IsAntiAntiDebugEnabled) this.Enable(); #endif } } /// /// Gets a value indicating whether the anti-debugging is enabled. /// public bool IsEnabled { get; private set; } = false; /// /// Enables the anti-debugging by overwriting code in memory. /// public void Enable() { this.original = new byte[this.nop.Length]; if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) { Log.Information($"Overwriting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original); SafeMemory.WriteBytes(this.debugCheckAddress, this.nop); } else { Log.Information("Debug check already overwritten?"); } this.IsEnabled = true; } /// /// Disable the anti-debugging by reverting the overwritten code in memory. /// public void Disable() { if (this.debugCheckAddress != IntPtr.Zero && this.original != null) { Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); SafeMemory.WriteBytes(this.debugCheckAddress, this.original); } else { Log.Information("Debug check was not overwritten?"); } this.IsEnabled = false; } } /// /// Implementing IDisposable. /// internal sealed partial class AntiDebug : IDisposable { private bool disposed = false; /// /// Finalizes an instance of the class. /// ~AntiDebug() => this.Dispose(false); /// /// Disposes of managed and unmanaged resources. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Disposes of managed and unmanaged resources. /// /// If this was disposed through calling Dispose() or from being finalized. 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; } }