diff --git a/Dalamud/Configuration/PluginConfigurations.cs b/Dalamud/Configuration/PluginConfigurations.cs
index 6a9c767f1..5fa1d54cf 100644
--- a/Dalamud/Configuration/PluginConfigurations.cs
+++ b/Dalamud/Configuration/PluginConfigurations.cs
@@ -55,9 +55,8 @@ namespace Dalamud.Configuration
File.ReadAllText(path.FullName),
new JsonSerializerSettings
{
- TypeNameAssemblyFormatHandling =
- TypeNameAssemblyFormatHandling.Simple,
- TypeNameHandling = TypeNameHandling.Objects,
+ TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
+ TypeNameHandling = TypeNameHandling.Objects,
});
}
@@ -108,8 +107,8 @@ namespace Dalamud.Configuration
///
/// InternalName of the plugin.
/// FileInfo of the config file.
- public FileInfo GetConfigFile(string pluginName) => new FileInfo(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
+ public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
- private DirectoryInfo GetDirectoryPath(string pluginName) => new DirectoryInfo(Path.Combine(this.configDirectory.FullName, pluginName));
+ private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName));
}
}
diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs
index b6367752f..7c98e6a8e 100644
--- a/Dalamud/Dalamud.cs
+++ b/Dalamud/Dalamud.cs
@@ -3,15 +3,16 @@ using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+
using Dalamud.Configuration;
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.Addon;
-using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.ClientState;
using Dalamud.Game.Command;
using Dalamud.Game.Internal;
using Dalamud.Game.Network;
+using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
using Dalamud.Plugin;
using Serilog;
@@ -182,12 +183,15 @@ namespace Dalamud
///
internal bool IsReady { get; private set; }
+ ///
+ /// Gets a value indicating whether the plugin system is loaded.
+ ///
internal bool IsLoadedPluginSystem => this.PluginManager != null;
///
/// Gets location of stored assets.
///
- internal DirectoryInfo AssetDirectory => new DirectoryInfo(this.StartInfo.AssetDirectory);
+ internal DirectoryInfo AssetDirectory => new(this.StartInfo.AssetDirectory);
///
/// Runs tier 1 of the Dalamud initialization process.
@@ -231,7 +235,6 @@ namespace Dalamud
Log.Information("[T2] AntiDebug OK!");
-
this.WinSock2 = new WinSockHandlers();
Log.Information("[T2] WinSock OK!");
@@ -384,7 +387,7 @@ namespace Dalamud
}
///
- /// Wait for a queued unload to be finalized.
+ /// Wait for a queued unload to be finalized.
///
public void WaitForUnloadFinish()
{
@@ -417,7 +420,7 @@ namespace Dalamud
}
///
- /// Dispose Dalamud subsystems.
+ /// Dispose Dalamud subsystems.
///
public void Dispose()
{
@@ -453,12 +456,11 @@ namespace Dalamud
}
///
- /// Replace the built-in exception handler with a debug one.
+ /// Replace the built-in exception handler with a debug one.
///
internal void ReplaceExceptionHandler()
{
- var releaseFilter = this.SigScanner.ScanText(
- "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??");
+ var releaseFilter = this.SigScanner.ScanText("40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??");
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj
index 13abbe738..501c36869 100644
--- a/Dalamud/Dalamud.csproj
+++ b/Dalamud/Dalamud.csproj
@@ -1,4 +1,4 @@
-
+
AnyCPU
net472
@@ -34,6 +34,18 @@
DEBUG;TRACE
+
+ IDE0017;IDE0044;IDE0047;IDE0048;IDE1006;CS1573;CS1591;CS1701;CS1702
+
+
+
+
+
+
+
+
+
+
@@ -54,7 +66,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs
index 89a57beb8..78ceca446 100644
--- a/Dalamud/DalamudStartInfo.cs
+++ b/Dalamud/DalamudStartInfo.cs
@@ -1,5 +1,4 @@
using System;
-#pragma warning disable SA1401 // Fields should be private
namespace Dalamud
{
@@ -50,5 +49,3 @@ namespace Dalamud
public bool OptOutMbCollection;
}
}
-
-#pragma warning restore SA1401 // Fields should be private
diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs
index f0504c16c..a6e4016e5 100644
--- a/Dalamud/Data/DataManager.cs
+++ b/Dalamud/Data/DataManager.cs
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
+
using Dalamud.Data.LuminaExtensions;
using Dalamud.Interface;
using ImGuiScene;
@@ -22,8 +23,8 @@ namespace Dalamud.Data
///
public class DataManager : IDisposable
{
- private readonly InterfaceManager interfaceManager;
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
+ private readonly InterfaceManager interfaceManager;
///
/// A object which gives access to any excel/game data.
@@ -36,6 +37,7 @@ namespace Dalamud.Data
/// Initializes a new instance of the class.
///
/// The language to load data with by default.
+ /// An instance to parse the data with.
internal DataManager(ClientLanguage language, InterfaceManager interfaceManager)
{
this.interfaceManager = interfaceManager;
@@ -71,87 +73,6 @@ namespace Dalamud.Data
///
public bool IsDataReady { get; private set; }
- ///
- /// Initialize this data manager.
- ///
- /// The directory to load data from.
- internal void Initialize(string baseDir)
- {
- try
- {
- Log.Verbose("Starting data load...");
-
- var zoneOpCodeDict =
- JsonConvert.DeserializeObject>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
- this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict);
-
- Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
-
- var clientOpCodeDict =
- JsonConvert.DeserializeObject>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
- this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict);
-
- Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
-
- var luminaOptions = new LuminaOptions
- {
- CacheFileResources = true,
-
-#if DEBUG
- PanicOnSheetChecksumMismatch = true,
-#else
- PanicOnSheetChecksumMismatch = false,
-#endif
-
- DefaultExcelLanguage = this.Language switch {
- ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
- ClientLanguage.English => Lumina.Data.Language.English,
- ClientLanguage.German => Lumina.Data.Language.German,
- ClientLanguage.French => Lumina.Data.Language.French,
- _ => throw new ArgumentOutOfRangeException(
- nameof(this.Language),
- @"Unknown Language: " + this.Language),
- },
- };
-
- var processModule = Process.GetCurrentProcess().MainModule;
- if (processModule != null)
- {
- this.gameData =
- new GameData(
- Path.Combine(
- Path.GetDirectoryName(processModule.FileName) !,
- "sqpack"), luminaOptions);
- }
-
- Log.Information("Lumina is ready: {0}", this.gameData.DataPath);
-
- this.IsDataReady = true;
-
- this.luminaResourceThread = new Thread(() =>
- {
- while (true)
- {
- if (this.gameData.FileHandleManager.HasPendingFileLoads)
- {
- this.gameData.ProcessFileHandleQueue();
- }
- else
- {
- Thread.Sleep(5);
- }
- }
-
- // ReSharper disable once FunctionNeverReturns
- });
- this.luminaResourceThread.Start();
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Could not download data.");
- }
- }
-
#region Lumina Wrappers
///
@@ -172,12 +93,13 @@ namespace Dalamud.Data
/// The , giving access to game rows.
public ExcelSheet GetExcelSheet(ClientLanguage language) where T : ExcelRow
{
- var lang = language switch {
+ var lang = language switch
+ {
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
ClientLanguage.English => Lumina.Data.Language.English,
ClientLanguage.German => Lumina.Data.Language.German,
ClientLanguage.French => Lumina.Data.Language.French,
- _ => throw new ArgumentOutOfRangeException(nameof(this.Language), @"Unknown Language: " + this.Language)
+ _ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
};
return this.Excel.GetSheet(lang);
}
@@ -203,7 +125,7 @@ namespace Dalamud.Data
var filePath = GameData.ParseFilePath(path);
if (filePath == null)
return default;
- return this.gameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default(T);
+ return this.gameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default;
}
///
@@ -234,12 +156,13 @@ namespace Dalamud.Data
/// The containing the icon.
public TexFile GetIcon(ClientLanguage iconLanguage, int iconId)
{
- var type = iconLanguage switch {
+ var type = iconLanguage switch
+ {
ClientLanguage.Japanese => "ja/",
ClientLanguage.English => "en/",
ClientLanguage.German => "de/",
ClientLanguage.French => "fr/",
- _ => throw new ArgumentOutOfRangeException(nameof(this.Language), @"Unknown Language: " + this.Language)
+ _ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
};
return this.GetIcon(type, iconId);
@@ -273,15 +196,16 @@ namespace Dalamud.Data
///
/// The Lumina .
/// A that can be used to draw the texture.
- public TextureWrap GetImGuiTexture(TexFile tex) =>
- this.interfaceManager.LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
+ public TextureWrap GetImGuiTexture(TexFile tex)
+ => this.interfaceManager.LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
///
/// Get the passed texture path as a drawable ImGui TextureWrap.
///
/// The internal path to the texture.
/// A that can be used to draw the texture.
- public TextureWrap GetImGuiTexture(string path) => this.GetImGuiTexture(this.GetFile(path));
+ public TextureWrap GetImGuiTexture(string path)
+ => this.GetImGuiTexture(this.GetFile(path));
///
/// Get a containing the icon with the given ID, of the given language.
@@ -289,8 +213,8 @@ namespace Dalamud.Data
/// The requested language.
/// The icon ID.
/// The containing the icon.
- public TextureWrap GetImGuiTextureIcon(ClientLanguage iconLanguage, int iconId) =>
- this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
+ public TextureWrap GetImGuiTextureIcon(ClientLanguage iconLanguage, int iconId)
+ => this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
///
/// Get a containing the icon with the given ID, of the given type.
@@ -298,8 +222,8 @@ namespace Dalamud.Data
/// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).
/// The icon ID.
/// The containing the icon.
- public TextureWrap GetImGuiTextureIcon(string type, int iconId) =>
- this.GetImGuiTexture(this.GetIcon(type, iconId));
+ public TextureWrap GetImGuiTextureIcon(string type, int iconId)
+ => this.GetImGuiTexture(this.GetIcon(type, iconId));
#endregion
@@ -310,5 +234,83 @@ namespace Dalamud.Data
{
this.luminaResourceThread.Abort();
}
+
+ ///
+ /// Initialize this data manager.
+ ///
+ /// The directory to load data from.
+ internal void Initialize(string baseDir)
+ {
+ try
+ {
+ Log.Verbose("Starting data load...");
+
+ var zoneOpCodeDict =
+ JsonConvert.DeserializeObject>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
+ this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict);
+
+ Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
+
+ var clientOpCodeDict =
+ JsonConvert.DeserializeObject>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
+ this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict);
+
+ Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
+
+ var luminaOptions = new LuminaOptions
+ {
+ CacheFileResources = true,
+
+#if DEBUG
+ PanicOnSheetChecksumMismatch = true,
+#else
+ PanicOnSheetChecksumMismatch = false,
+#endif
+
+ DefaultExcelLanguage = this.Language switch
+ {
+ ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
+ ClientLanguage.English => Lumina.Data.Language.English,
+ ClientLanguage.German => Lumina.Data.Language.German,
+ ClientLanguage.French => Lumina.Data.Language.French,
+ _ => throw new ArgumentOutOfRangeException(
+ nameof(this.Language),
+ @"Unknown Language: " + this.Language),
+ },
+ };
+
+ var processModule = Process.GetCurrentProcess().MainModule;
+ if (processModule != null)
+ {
+ this.gameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions);
+ }
+
+ Log.Information("Lumina is ready: {0}", this.gameData.DataPath);
+
+ this.IsDataReady = true;
+
+ this.luminaResourceThread = new Thread(() =>
+ {
+ while (true)
+ {
+ if (this.gameData.FileHandleManager.HasPendingFileLoads)
+ {
+ this.gameData.ProcessFileHandleQueue();
+ }
+ else
+ {
+ Thread.Sleep(5);
+ }
+ }
+
+ // ReSharper disable once FunctionNeverReturns
+ });
+ this.luminaResourceThread.Start();
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Could not download data.");
+ }
+ }
}
}
diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs
index a1697e9b7..39fe1e38b 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -77,7 +77,7 @@ namespace Dalamud
}
}
- private (Logger logger, LoggingLevelSwitch levelSwitch) NewLogger(string baseDirectory)
+ private (Logger Logger, LoggingLevelSwitch LevelSwitch) NewLogger(string baseDirectory)
{
#if DEBUG
var logPath = Path.Combine(baseDirectory, "dalamud.log");
@@ -95,7 +95,7 @@ namespace Dalamud
var newLogger = new LoggerConfiguration()
.WriteTo.Async(a => a.File(logPath))
- .WriteTo.EventSink()
+ .WriteTo.Sink(SerilogEventSink.Instance)
.MinimumLevel.ControlledBy(levelSwitch)
.CreateLogger();
diff --git a/Dalamud/Game/Addon/DalamudSystemMenu.cs b/Dalamud/Game/Addon/DalamudSystemMenu.cs
index 7532463d6..a32024d6e 100644
--- a/Dalamud/Game/Addon/DalamudSystemMenu.cs
+++ b/Dalamud/Game/Addon/DalamudSystemMenu.cs
@@ -10,24 +10,15 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Dalamud.Game.Addon
{
- internal unsafe class DalamudSystemMenu
+ ///
+ /// This class implements in-game Dalamud options in the in-game System menu.
+ ///
+ internal sealed unsafe partial class DalamudSystemMenu
{
private readonly Dalamud dalamud;
-
- private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
-
- private Hook hookAgentHudOpenSystemMenu;
-
- private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type);
-
private AtkValueChangeType atkValueChangeType;
-
- private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes);
-
private AtkValueSetString atkValueSetString;
-
- private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
-
+ private Hook hookAgentHudOpenSystemMenu;
// TODO: Make this into events in Framework.Gui
private Hook hookUiModuleRequestMainCommand;
@@ -55,14 +46,24 @@ namespace Dalamud.Game.Addon
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer(atkValueSetStringAddress);
- var uiModuleRequestMainCommandAddress = this.dalamud.SigScanner.ScanText(
- "40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
+ var uiModuleRequestMainCommandAddress = this.dalamud.SigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
this.hookUiModuleRequestMainCommand = new Hook(
uiModuleRequestMainCommandAddress,
new UiModuleRequestMainCommand(this.UiModuleRequestMainCommandDetour),
this);
}
+ private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
+
+ private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type);
+
+ private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes);
+
+ private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
+
+ ///
+ /// Enables the .
+ ///
public void Enable()
{
this.hookAgentHudOpenSystemMenu.Enable();
@@ -95,7 +96,7 @@ namespace Dalamud.Game.Addon
this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int
this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int);
- for (uint i = menuSize + 2; i > 1; i--)
+ for (var i = menuSize + 2; i > 1; i--)
{
var curEntry = &atkValueArgs[i + 5 - 2];
var nextEntry = &atkValueArgs[i + 5];
@@ -155,21 +156,44 @@ namespace Dalamud.Game.Addon
break;
}
}
+ }
- #region IDisposable Support
- protected virtual void Dispose(bool disposing)
- {
- if (!disposing) return;
+ ///
+ /// Implements IDisposable.
+ ///
+ internal sealed partial class DalamudSystemMenu : IDisposable
+ {
+ private bool disposed = false;
- this.hookAgentHudOpenSystemMenu.Dispose();
- this.hookUiModuleRequestMainCommand.Dispose();
- }
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~DalamudSystemMenu() => this.Dispose(false);
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
public void Dispose()
{
- Dispose(true);
+ this.Dispose(true);
GC.SuppressFinalize(this);
}
- #endregion
+
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ private void Dispose(bool disposing)
+ {
+ if (this.disposed)
+ return;
+
+ if (disposing)
+ {
+ this.hookAgentHudOpenSystemMenu.Dispose();
+ this.hookUiModuleRequestMainCommand.Dispose();
+ }
+
+ this.disposed = true;
+ }
}
}
diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs
index 05f290a40..5beb4c5ed 100644
--- a/Dalamud/Game/ChatHandlers.cs
+++ b/Dalamud/Game/ChatHandlers.cs
@@ -6,111 +6,156 @@ using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+
using CheapLoc;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
-using Dalamud.Game.Internal.Libc;
using Dalamud.Interface;
-using Dalamud.Plugin;
using Serilog;
-namespace Dalamud.Game {
- public class ChatHandlers {
- private static readonly Dictionary UnicodeToDiscordEmojiDict = new Dictionary {
- {"", "<:ffxive071:585847382210642069>"},
- {"", "<:ffxive083:585848592699490329>"}
+namespace Dalamud.Game
+{
+ ///
+ /// Chat events and public helper functions.
+ ///
+ public class ChatHandlers
+ {
+ private static readonly Dictionary UnicodeToDiscordEmojiDict = new()
+ {
+ { "", "<:ffxive071:585847382210642069>" },
+ { "", "<:ffxive083:585848592699490329>" },
};
- private readonly Dalamud dalamud;
-
- private DalamudLinkPayload openInstallerWindowLink;
-
- private readonly Dictionary HandledChatTypeColors = new Dictionary {
- {XivChatType.CrossParty, Color.DodgerBlue},
- {XivChatType.Party, Color.DodgerBlue},
- {XivChatType.FreeCompany, Color.DeepSkyBlue},
- {XivChatType.CrossLinkShell1, Color.ForestGreen},
- {XivChatType.CrossLinkShell2, Color.ForestGreen},
- {XivChatType.CrossLinkShell3, Color.ForestGreen},
- {XivChatType.CrossLinkShell4, Color.ForestGreen},
- {XivChatType.CrossLinkShell5, Color.ForestGreen},
- {XivChatType.CrossLinkShell6, Color.ForestGreen},
- {XivChatType.CrossLinkShell7, Color.ForestGreen},
- {XivChatType.CrossLinkShell8, Color.ForestGreen},
- {XivChatType.Ls1, Color.ForestGreen},
- {XivChatType.Ls2, Color.ForestGreen},
- {XivChatType.Ls3, Color.ForestGreen},
- {XivChatType.Ls4, Color.ForestGreen},
- {XivChatType.Ls5, Color.ForestGreen},
- {XivChatType.Ls6, Color.ForestGreen},
- {XivChatType.Ls7, Color.ForestGreen},
- {XivChatType.Ls8, Color.ForestGreen},
- {XivChatType.TellIncoming, Color.HotPink},
- {XivChatType.PvPTeam, Color.SandyBrown},
- {XivChatType.Urgent, Color.DarkViolet},
- {XivChatType.NoviceNetwork, Color.SaddleBrown},
- {XivChatType.Echo, Color.Gray}
+ private readonly Dictionary handledChatTypeColors = new()
+ {
+ { XivChatType.CrossParty, Color.DodgerBlue },
+ { XivChatType.Party, Color.DodgerBlue },
+ { XivChatType.FreeCompany, Color.DeepSkyBlue },
+ { XivChatType.CrossLinkShell1, Color.ForestGreen },
+ { XivChatType.CrossLinkShell2, Color.ForestGreen },
+ { XivChatType.CrossLinkShell3, Color.ForestGreen },
+ { XivChatType.CrossLinkShell4, Color.ForestGreen },
+ { XivChatType.CrossLinkShell5, Color.ForestGreen },
+ { XivChatType.CrossLinkShell6, Color.ForestGreen },
+ { XivChatType.CrossLinkShell7, Color.ForestGreen },
+ { XivChatType.CrossLinkShell8, Color.ForestGreen },
+ { XivChatType.Ls1, Color.ForestGreen },
+ { XivChatType.Ls2, Color.ForestGreen },
+ { XivChatType.Ls3, Color.ForestGreen },
+ { XivChatType.Ls4, Color.ForestGreen },
+ { XivChatType.Ls5, Color.ForestGreen },
+ { XivChatType.Ls6, Color.ForestGreen },
+ { XivChatType.Ls7, Color.ForestGreen },
+ { XivChatType.Ls8, Color.ForestGreen },
+ { XivChatType.TellIncoming, Color.HotPink },
+ { XivChatType.PvPTeam, Color.SandyBrown },
+ { XivChatType.Urgent, Color.DarkViolet },
+ { XivChatType.NoviceNetwork, Color.SaddleBrown },
+ { XivChatType.Echo, Color.Gray },
};
- private readonly Regex rmtRegex =
- new Regex(
+ private readonly Regex rmtRegex = new(
@"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia",
RegexOptions.Compiled);
- private readonly Dictionary retainerSaleRegexes = new Dictionary() { {
- ClientLanguage.Japanese, new Regex[] {
+ private readonly Dictionary retainerSaleRegexes = new()
+ {
+ {
+ ClientLanguage.Japanese,
+ new Regex[]
+ {
new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?- .*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
- new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?
- .*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled) }
- }, {
- ClientLanguage.English, new Regex[] {
- new Regex(@"^(?
- .+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled)
+ new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?
- .*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
}
- }, {
- ClientLanguage.German, new Regex[] {
+ },
+ {
+ ClientLanguage.English,
+ new Regex[]
+ {
+ new Regex(@"^(?
- .+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled),
+ }
+ },
+ {
+ ClientLanguage.German,
+ new Regex[]
+ {
new Regex(@"^Dein Gehilfe hat (?
- .+) auf dem Markt von (?:.+) für (?[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
- new Regex(@"^Dein Gehilfe hat (?
- .+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled)
+ new Regex(@"^Dein Gehilfe hat (?
- .+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
}
- }, {
- ClientLanguage.French, new Regex[] {
- new Regex(@"^Un servant a vendu (?
- .+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled)
+ },
+ {
+ ClientLanguage.French,
+ new Regex[]
+ {
+ new Regex(@"^Un servant a vendu (?
- .+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
}
- }
+ },
};
- private readonly Regex urlRegex =
- new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?",
- RegexOptions.Compiled);
+ private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
+ private readonly Dalamud dalamud;
+ private DalamudLinkPayload openInstallerWindowLink;
private bool hasSeenLoadingMsg;
- public string LastLink { get; private set; }
-
- public ChatHandlers(Dalamud dalamud) {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Dalamud instance.
+ public ChatHandlers(Dalamud dalamud)
+ {
this.dalamud = dalamud;
-
- dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
- dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
- this.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) => {
+ dalamud.Framework.Gui.Chat.OnCheckMessageHandled += this.OnCheckMessageHandled;
+ dalamud.Framework.Gui.Chat.OnChatMessage += this.OnChatMessage;
+
+ this.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
+ {
this.dalamud.DalamudUi.OpenPluginInstaller();
});
}
- private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) {
+ ///
+ /// Gets the last URL seen in chat.
+ ///
+ public string LastLink { get; private set; }
+
+ ///
+ /// Convert a string to SeString and wrap in italics payloads.
+ ///
+ /// Text to convert.
+ /// SeString payload of italicized text.
+ private static SeString MakeItalics(string text)
+ {
+ // TODO: when the code OnCharMessage is switched to SeString, this can be a straight insertion of the
+ // italics payloads only, and be a lot cleaner
+ var italicString = new SeString(new List(new Payload[]
+ {
+ EmphasisItalicPayload.ItalicsOn,
+ new TextPayload(text),
+ EmphasisItalicPayload.ItalicsOff,
+ }));
+
+ return italicString;
+ }
+
+ private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
+ {
var textVal = message.TextValue;
var matched = this.rmtRegex.IsMatch(textVal);
- if (matched) {
+ if (matched)
+ {
// This seems to be a RMT ad - let's not show it
Log.Debug("Handled RMT ad: " + message.TextValue);
isHandled = true;
return;
}
-
if (this.dalamud.Configuration.BadWords != null &&
- this.dalamud.Configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) {
+ this.dalamud.Configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
+ {
// This seems to be in the user block list - let's not show it
Log.Debug("Blocklist triggered");
isHandled = true;
@@ -118,23 +163,24 @@ namespace Dalamud.Game {
}
}
- private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender,
- ref SeString message, ref bool isHandled) {
-
- if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
- PrintWelcomeMessage();
+ private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
+ {
+ if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
+ this.PrintWelcomeMessage();
// For injections while logged in
if (this.dalamud.ClientState.LocalPlayer != null && this.dalamud.ClientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
- PrintWelcomeMessage();
+ this.PrintWelcomeMessage();
#if !DEBUG && false
if (!this.hasSeenLoadingMsg)
return;
#endif
- if (type == XivChatType.RetainerSale) {
- foreach (var regex in retainerSaleRegexes[dalamud.StartInfo.Language]) {
+ if (type == XivChatType.RetainerSale)
+ {
+ foreach (var regex in this.retainerSaleRegexes[this.dalamud.StartInfo.Language])
+ {
var matchInfo = regex.Match(message.TextValue);
// we no longer really need to do/validate the item matching since we read the id from the byte array
@@ -143,10 +189,9 @@ namespace Dalamud.Game {
if (!itemInfo.Success)
continue;
- var itemLink =
- message.Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload;
-
- if (itemLink == null) {
+ var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
+ if (itemLink == default)
+ {
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
break;
}
@@ -155,10 +200,10 @@ namespace Dalamud.Game {
var valueInfo = matchInfo.Groups["value"];
// not sure if using a culture here would work correctly, so just strip symbols instead
- if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue))
+ if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue))
continue;
- //Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
+ // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
break;
}
}
@@ -168,7 +213,7 @@ namespace Dalamud.Game {
var linkMatch = this.urlRegex.Match(message.TextValue);
if (linkMatch.Value.Length > 0)
- LastLink = linkMatch.Value;
+ this.LastLink = linkMatch.Value;
// Handle all of this with SeString some day
/*
@@ -193,43 +238,60 @@ namespace Dalamud.Game {
*/
}
- private void PrintWelcomeMessage() {
+ private void PrintWelcomeMessage()
+ {
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), this.dalamud.PluginManager.Plugins.Count));
- if (this.dalamud.Configuration.PrintPluginsWelcomeMsg) {
- foreach (var plugin in this.dalamud.PluginManager.Plugins.OrderBy(x => x.Plugin.Name)) {
+ if (this.dalamud.Configuration.PrintPluginsWelcomeMsg)
+ {
+ foreach (var plugin in this.dalamud.PluginManager.Plugins.OrderBy(x => x.Plugin.Name))
+ {
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Plugin.Name, plugin.Definition.AssemblyVersion));
}
}
- if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion)) {
- this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
+ if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion))
+ {
+ this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry
+ {
MessageBytes = Encoding.UTF8.GetBytes(Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog.")),
- Type = XivChatType.Notice
+ Type = XivChatType.Notice,
});
if (DalamudChangelogWindow.WarrantsChangelog)
+#pragma warning disable CS0162 // Unreachable code detected
this.dalamud.DalamudUi.OpenChangelog();
+#pragma warning restore CS0162 // Unreachable code detected
this.dalamud.Configuration.LastVersion = assemblyVersion;
this.dalamud.Configuration.Save();
}
- Task.Run(() => this.dalamud.PluginRepository.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins)).ContinueWith(t => {
- if (t.IsFaulted) {
+ Task.Run(() => this.dalamud.PluginRepository.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins)).ContinueWith(t =>
+ {
+ if (t.IsFaulted)
+ {
Log.Error(t.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
- } else {
+ }
+ else
+ {
var updatedPlugins = t.Result.UpdatedPlugins;
- if (updatedPlugins != null && updatedPlugins.Any()) {
- if (this.dalamud.Configuration.AutoUpdatePlugins) {
+ if (updatedPlugins != null && updatedPlugins.Any())
+ {
+ if (this.dalamud.Configuration.AutoUpdatePlugins)
+ {
this.dalamud.PluginRepository.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
- } else {
- this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
- MessageBytes = new SeString(new List() {
+ }
+ else
+ {
+ this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry
+ {
+ MessageBytes = new SeString(new List()
+ {
new TextPayload(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")),
new TextPayload(" ["),
new UIForegroundPayload(this.dalamud.Data, 500),
@@ -239,7 +301,7 @@ namespace Dalamud.Game {
new UIForegroundPayload(this.dalamud.Data, 0),
new TextPayload("]"),
}).Encode(),
- Type = XivChatType.Urgent
+ Type = XivChatType.Urgent,
});
}
}
@@ -248,17 +310,5 @@ namespace Dalamud.Game {
this.hasSeenLoadingMsg = true;
}
-
- private static SeString MakeItalics(string text) {
- // TODO: when the above code is switched to SeString, this can be a straight insertion of the
- // italics payloads only, and be a lot cleaner
- var italicString = new SeString(new List(new Payload[] {
- EmphasisItalicPayload.ItalicsOn,
- new TextPayload(text),
- EmphasisItalicPayload.ItalicsOff
- }));
-
- return italicString;
- }
}
}
diff --git a/Dalamud/Game/ClientState/Actors/ActorTable.cs b/Dalamud/Game/ClientState/Actors/ActorTable.cs
index c6746d29c..7f8ca7c35 100644
--- a/Dalamud/Game/ClientState/Actors/ActorTable.cs
+++ b/Dalamud/Game/ClientState/Actors/ActorTable.cs
@@ -1,170 +1,224 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
+
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
using JetBrains.Annotations;
using Serilog;
-namespace Dalamud.Game.ClientState.Actors {
+namespace Dalamud.Game.ClientState.Actors
+{
///
- /// This collection represents the currently spawned FFXIV actors.
+ /// This collection represents the currently spawned FFXIV actors.
///
- public class ActorTable : IReadOnlyCollection, ICollection, IDisposable {
-
+ public sealed partial class ActorTable : IReadOnlyCollection, ICollection, IDisposable
+ {
private const int ActorTableLength = 424;
- #region Actor Table Cache
+ #region ReadProcessMemory Hack
+ private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
+ private static readonly IntPtr ActorMem = Marshal.AllocHGlobal(ActorMemSize);
+ private static readonly IntPtr CurrentProcessHandle = new(-1);
+ #endregion
+
+ private Dalamud dalamud;
+ private ClientStateAddressResolver address;
private List actorsCache;
- private List ActorsCache {
- get {
- if (this.actorsCache != null) return this.actorsCache;
- this.actorsCache = GetActorTable();
- return this.actorsCache;
- }
- }
-
- private void ResetCache() => actorsCache = null;
- #endregion
-
- #region ReadProcessMemory Hack
-
- [DllImport("kernel32.dll", SetLastError = true)]
- static extern bool ReadProcessMemory(
- IntPtr hProcess,
- IntPtr lpBaseAddress,
- IntPtr lpBuffer,
- int dwSize,
- out IntPtr lpNumberOfBytesRead);
-
- private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
- private IntPtr actorMem = Marshal.AllocHGlobal(ActorMemSize);
- private IntPtr currentProcessHandle = new IntPtr(-1);
-
- #endregion
-
- private ClientStateAddressResolver Address { get; }
- private Dalamud dalamud;
-
///
- /// Set up the actor table collection.
+ /// Initializes a new instance of the class.
+ /// Set up the actor table collection.
///
- /// Client state address resolver.
- public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
- Address = addressResolver;
+ /// The Dalamud instance.
+ /// The ClientStateAddressResolver instance.
+ public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
+ {
+ this.address = addressResolver;
this.dalamud = dalamud;
- dalamud.Framework.OnUpdateEvent += Framework_OnUpdateEvent;
+ dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent;
- Log.Verbose("Actor table address {ActorTable}", Address.ActorTable);
- }
-
- private void Framework_OnUpdateEvent(Internal.Framework framework) {
- this.ResetCache();
+ Log.Verbose("Actor table address {ActorTable}", this.address.ActorTable);
}
///
- /// Get an actor at the specified spawn index.
+ /// Gets the amount of currently spawned actors.
+ ///
+ public int Length => this.ActorsCache.Count;
+
+ private List ActorsCache => this.actorsCache ??= this.GetActorTable();
+
+ ///
+ /// Get an actor at the specified spawn index.
///
/// Spawn index.
/// at the specified spawn index.
[CanBeNull]
- public Actor this[int index] {
- get => ActorsCache[index];
- }
+ public Actor this[int index] => this.ActorsCache[index];
+ ///
+ /// Read an actor struct from memory and create the appropriate type of actor.
+ ///
+ /// Offset of the actor in the actor table.
+ /// An instantiated actor.
internal Actor ReadActorFromMemory(IntPtr offset)
{
- try {
+ try
+ {
// FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
- if (!ReadProcessMemory(this.currentProcessHandle, offset, this.actorMem, ActorMemSize, out _))
+ if (!NativeFunctions.ReadProcessMemory(CurrentProcessHandle, offset, ActorMem, ActorMemSize, out _))
{
Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
return null;
}
- var actorStruct = Marshal.PtrToStructure(this.actorMem);
+ var actorStruct = Marshal.PtrToStructure(ActorMem);
- return actorStruct.ObjectKind switch {
+ return actorStruct.ObjectKind switch
+ {
ObjectKind.Player => new PlayerCharacter(offset, actorStruct, this.dalamud),
ObjectKind.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud),
ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud),
ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud),
- _ => new Actor(offset, actorStruct, this.dalamud)
+ _ => new Actor(offset, actorStruct, this.dalamud),
};
}
- catch (Exception e) {
+ catch (Exception e)
+ {
Log.Error(e, "Could not read actor from memory.");
return null;
}
}
- private IntPtr[] GetPointerTable() {
+ private void ResetCache() => this.actorsCache = null;
+
+ private void Framework_OnUpdateEvent(Internal.Framework framework)
+ {
+ this.ResetCache();
+ }
+
+ private IntPtr[] GetPointerTable()
+ {
var ret = new IntPtr[ActorTableLength];
- Marshal.Copy(Address.ActorTable, ret, 0, ActorTableLength);
+ Marshal.Copy(this.address.ActorTable, ret, 0, ActorTableLength);
return ret;
}
- private List GetActorTable() {
+ private List GetActorTable()
+ {
var actors = new List();
- var ptrTable = GetPointerTable();
- for (var i = 0; i < ActorTableLength; i++) {
- actors.Add(ptrTable[i] != IntPtr.Zero ? ReadActorFromMemory(ptrTable[i]) : null);
+ var ptrTable = this.GetPointerTable();
+ for (var i = 0; i < ActorTableLength; i++)
+ {
+ actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null);
}
+
return actors;
}
+ }
- public IEnumerator GetEnumerator() {
- return ActorsCache.Where(a => a != null).GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator() {
- return GetEnumerator();
- }
+ ///
+ /// Implementing IDisposable.
+ ///
+ public sealed partial class ActorTable : IDisposable
+ {
+ private bool disposed = false;
///
- /// The amount of currently spawned actors.
+ /// Finalizes an instance of the class.
///
- public int Length => ActorsCache.Count;
+ ~ActorTable() => this.Dispose(false);
- int IReadOnlyCollection.Count => Length;
+ ///
+ /// Disposes of managed and unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- int ICollection.Count => Length;
+ private void Dispose(bool disposing)
+ {
+ if (this.disposed)
+ return;
+ if (disposing)
+ {
+ this.dalamud.Framework.OnUpdateEvent -= this.Framework_OnUpdateEvent;
+ Marshal.FreeHGlobal(ActorMem);
+ }
+
+ this.disposed = true;
+ }
+ }
+
+ ///
+ /// Implementing IReadOnlyCollection, IEnumerable, and Enumerable.
+ ///
+ public sealed partial class ActorTable : IReadOnlyCollection
+ {
+ ///
+ /// Gets the number of elements in the collection.
+ ///
+ /// The number of elements in the collection.
+ int IReadOnlyCollection.Count => this.Length;
+
+ ///
+ /// Gets an enumerator capable of iterating through the actor table.
+ ///
+ /// An actor enumerable.
+ public IEnumerator GetEnumerator() => this.ActorsCache.Where(a => a != null).GetEnumerator();
+
+ ///
+ /// Gets an enumerator capable of iterating through the actor table.
+ ///
+ /// An actor enumerable.
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ }
+
+ ///
+ /// Implementing ICollection.
+ ///
+ public sealed partial class ActorTable : ICollection
+ {
+ ///
+ /// Gets the number of elements in the collection.
+ ///
+ /// The number of elements in the collection.
+ int ICollection.Count => this.Length;
+
+ ///
+ /// Gets a value indicating whether access to the collection is synchronized (thread safe).
+ ///
+ /// Whether access is synchronized (thread safe) or not.
bool ICollection.IsSynchronized => false;
+ ///
+ /// Gets an object that can be used to synchronize access to the collection.
+ ///
+ /// An object that can be used to synchronize access to the collection.
object ICollection.SyncRoot => this;
- void ICollection.CopyTo(Array array, int index) {
- for (var i = 0; i < Length; i++) {
+ ///
+ /// Copies the elements of the collection to an array, starting at a particular index.
+ ///
+ ///
+ /// The one-dimensional array that is the destination of the elements copied from the collection. The array must have zero-based indexing.
+ ///
+ ///
+ /// The zero-based index in array at which copying begins.
+ ///
+ void ICollection.CopyTo(Array array, int index)
+ {
+ for (var i = 0; i < this.Length; i++)
+ {
array.SetValue(this[i], index);
index++;
}
}
-
- #region IDisposable Pattern
- private bool disposed = false;
-
- private void Dispose(bool disposing)
- {
- if (this.disposed) return;
- this.dalamud.Framework.OnUpdateEvent -= Framework_OnUpdateEvent;
- Marshal.FreeHGlobal(this.actorMem);
- this.disposed = true;
- }
-
- public void Dispose() {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ~ActorTable() {
- Dispose(false);
- }
- #endregion
}
}
diff --git a/Dalamud/Game/ClientState/Actors/CustomizeIndex.cs b/Dalamud/Game/ClientState/Actors/CustomizeIndex.cs
index d29c2f2ec..2461a028a 100644
--- a/Dalamud/Game/ClientState/Actors/CustomizeIndex.cs
+++ b/Dalamud/Game/ClientState/Actors/CustomizeIndex.cs
@@ -1,16 +1,11 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Dalamud.Game.ClientState.Actors
{
///
/// This enum describes the indices of the Customize array.
///
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
- public enum CustomizeIndex {
+ public enum CustomizeIndex
+ {
///
/// The race of the character.
///
@@ -35,12 +30,12 @@ namespace Dalamud.Game.ClientState.Actors
/// The model type of the character.
///
ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features
-
+
///
/// The face type of the character.
///
FaceType = 0x05,
-
+
///
/// The hair of the character.
///
@@ -50,12 +45,12 @@ namespace Dalamud.Game.ClientState.Actors
/// Whether or not the character has hair highlights.
///
HasHighlights = 0x07, // negative to enable, positive to disable
-
+
///
/// The skin color of the character.
///
SkinColor = 0x08,
-
+
///
/// The eye color of the character.
///
@@ -125,17 +120,17 @@ namespace Dalamud.Game.ClientState.Actors
/// The race feature type of the character.
///
RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client
-
+
///
/// The bust size of the character.
///
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
-
+
///
/// The face paint of the character.
///
Facepaint = 0x18,
-
+
///
/// The face paint color of the character.
///
diff --git a/Dalamud/Game/ClientState/Actors/ObjectKind.cs b/Dalamud/Game/ClientState/Actors/ObjectKind.cs
index 48af1fdfb..4e62d812d 100644
--- a/Dalamud/Game/ClientState/Actors/ObjectKind.cs
+++ b/Dalamud/Game/ClientState/Actors/ObjectKind.cs
@@ -1,69 +1,83 @@
-namespace Dalamud.Game.ClientState.Actors {
+namespace Dalamud.Game.ClientState.Actors
+{
///
- /// Enum describing possible entity kinds.
+ /// Enum describing possible entity kinds.
///
- public enum ObjectKind : byte {
+ public enum ObjectKind : byte
+ {
///
- /// Invalid actor.
+ /// Invalid actor.
///
None = 0x00,
///
- /// Objects representing player characters.
+ /// Objects representing player characters.
///
Player = 0x01,
///
- /// Objects representing battle NPCs.
+ /// Objects representing battle NPCs.
///
BattleNpc = 0x02,
///
- /// Objects representing event NPCs.
+ /// Objects representing event NPCs.
///
EventNpc = 0x03,
///
- /// Objects representing treasures.
+ /// Objects representing treasures.
///
Treasure = 0x04,
///
- /// Objects representing aetherytes.
+ /// Objects representing aetherytes.
///
Aetheryte = 0x05,
///
- /// Objects representing gathering points.
+ /// Objects representing gathering points.
///
GatheringPoint = 0x06,
///
- /// Objects representing event objects.
+ /// Objects representing event objects.
///
EventObj = 0x07,
///
- /// Objects representing mounts.
+ /// Objects representing mounts.
///
MountType = 0x08,
///
- /// Objects representing minions.
+ /// Objects representing minions.
///
Companion = 0x09, // Minion
///
- /// Objects representing retainers.
+ /// Objects representing retainers.
///
Retainer = 0x0A,
+
+ ///
+ /// Objects representing area objects.
+ ///
Area = 0x0B,
///
- /// Objects representing housing objects.
+ /// Objects representing housing objects.
///
Housing = 0x0C,
+
+ ///
+ /// Objects representing cutscene objects.
+ ///
Cutscene = 0x0D,
- CardStand = 0x0E
+
+ ///
+ /// Objects representing card stand objects.
+ ///
+ CardStand = 0x0E,
}
}
diff --git a/Dalamud/Game/ClientState/Actors/Position3.cs b/Dalamud/Game/ClientState/Actors/Position3.cs
index df367a06b..df8b0c471 100644
--- a/Dalamud/Game/ClientState/Actors/Position3.cs
+++ b/Dalamud/Game/ClientState/Actors/Position3.cs
@@ -1,22 +1,38 @@
using System.Runtime.InteropServices;
-namespace Dalamud.Game.ClientState.Actors {
+namespace Dalamud.Game.ClientState.Actors
+{
+ ///
+ /// A game native equivalent of a Vector3.
+ ///
[StructLayout(LayoutKind.Sequential)]
- public struct Position3 {
+ public struct Position3
+ {
+ ///
+ /// The X of (X,Z,Y).
+ ///
public float X;
+
+ ///
+ /// The Z of (X,Z,Y).
+ ///
public float Z;
+
+ ///
+ /// The Y of (X,Z,Y).
+ ///
public float Y;
///
- /// Convert this Position3 to a System.Numerics.Vector3
+ /// Convert this Position3 to a System.Numerics.Vector3.
///
/// Position to convert.
- public static implicit operator System.Numerics.Vector3(Position3 pos) => new System.Numerics.Vector3(pos.X, pos.Y, pos.Z);
+ public static implicit operator System.Numerics.Vector3(Position3 pos) => new(pos.X, pos.Y, pos.Z);
///
- /// Convert this Position3 to a SharpDX.Vector3
+ /// Convert this Position3 to a SharpDX.Vector3.
///
/// Position to convert.
- public static implicit operator SharpDX.Vector3(Position3 pos) => new SharpDX.Vector3(pos.X, pos.Z, pos.Y);
+ public static implicit operator SharpDX.Vector3(Position3 pos) => new(pos.X, pos.Z, pos.Y);
}
}
diff --git a/Dalamud/Game/ClientState/Actors/Resolvers/BaseResolver.cs b/Dalamud/Game/ClientState/Actors/Resolvers/BaseResolver.cs
index af55336a1..c097b1111 100644
--- a/Dalamud/Game/ClientState/Actors/Resolvers/BaseResolver.cs
+++ b/Dalamud/Game/ClientState/Actors/Resolvers/BaseResolver.cs
@@ -1,17 +1,24 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
- public abstract class BaseResolver {
- protected Dalamud dalamud;
+ ///
+ /// Base object resolver.
+ ///
+ public abstract class BaseResolver
+ {
+ private Dalamud dalamud;
- public BaseResolver(Dalamud dalamud) {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Dalamud instance.
+ public BaseResolver(Dalamud dalamud)
+ {
this.dalamud = dalamud;
}
+
+ ///
+ /// Gets the Dalamud instance.
+ ///
+ protected Dalamud Dalamud => this.dalamud;
}
}
diff --git a/Dalamud/Game/ClientState/Actors/Resolvers/ClassJob.cs b/Dalamud/Game/ClientState/Actors/Resolvers/ClassJob.cs
index f2a323bda..57ae9fc48 100644
--- a/Dalamud/Game/ClientState/Actors/Resolvers/ClassJob.cs
+++ b/Dalamud/Game/ClientState/Actors/Resolvers/ClassJob.cs
@@ -1,32 +1,31 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
///
/// This object represents a class or job.
///
- public class ClassJob : BaseResolver {
+ public class ClassJob : BaseResolver
+ {
///
/// ID of the ClassJob.
///
public readonly uint Id;
///
- /// GameData linked to this ClassJob.
- ///
- public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
- this.dalamud.Data.GetExcelSheet().GetRow(this.Id);
-
- ///
+ /// Initializes a new instance of the class.
/// Set up the ClassJob resolver with the provided ID.
///
- /// The ID of the world.
- public ClassJob(byte id, Dalamud dalamud) : base(dalamud) {
+ /// The ID of the classJob.
+ /// The Dalamud instance.
+ public ClassJob(byte id, Dalamud dalamud)
+ : base(dalamud)
+ {
this.Id = id;
}
+
+ ///
+ /// Gets GameData linked to this ClassJob.
+ ///
+ public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
+ this.Dalamud.Data.GetExcelSheet().GetRow(this.Id);
}
}
diff --git a/Dalamud/Game/ClientState/Actors/Resolvers/World.cs b/Dalamud/Game/ClientState/Actors/Resolvers/World.cs
index 41ea07d04..536bfaf81 100644
--- a/Dalamud/Game/ClientState/Actors/Resolvers/World.cs
+++ b/Dalamud/Game/ClientState/Actors/Resolvers/World.cs
@@ -1,32 +1,31 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
///
/// This object represents a world a character can reside on.
///
- public class World : BaseResolver {
+ public class World : BaseResolver
+ {
///
/// ID of the world.
///
public readonly uint Id;
///
- /// GameData linked to this world.
- ///
- public Lumina.Excel.GeneratedSheets.World GameData =>
- this.dalamud.Data.GetExcelSheet().GetRow(this.Id);
-
- ///
+ /// Initializes a new instance of the class.
/// Set up the world resolver with the provided ID.
///
/// The ID of the world.
- public World(ushort id, Dalamud dalamud) : base(dalamud) {
+ /// The Dalamud instance.
+ public World(ushort id, Dalamud dalamud)
+ : base(dalamud)
+ {
this.Id = id;
}
+
+ ///
+ /// Gets GameData linked to this world.
+ ///
+ public Lumina.Excel.GeneratedSheets.World GameData =>
+ this.Dalamud.Data.GetExcelSheet().GetRow(this.Id);
}
}
diff --git a/Dalamud/Game/ClientState/Actors/Targets.cs b/Dalamud/Game/ClientState/Actors/Targets.cs
index 7591011cd..c9e8a5129 100644
--- a/Dalamud/Game/ClientState/Actors/Targets.cs
+++ b/Dalamud/Game/ClientState/Actors/Targets.cs
@@ -1,50 +1,118 @@
using System;
using System.Runtime.InteropServices;
+
using Dalamud.Game.ClientState.Actors.Types;
-namespace Dalamud.Game.ClientState.Actors {
- public static class TargetOffsets {
- public const int CurrentTarget = 0x80;
- public const int MouseOverTarget = 0xD0;
- public const int FocusTarget = 0xF8;
- public const int PreviousTarget = 0x110;
- public const int SoftTarget = 0x88;
- }
-
- public sealed class Targets {
- private ClientStateAddressResolver Address { get; }
+namespace Dalamud.Game.ClientState.Actors
+{
+ ///
+ /// Get and set various kinds of targets for the player.
+ ///
+ public sealed class Targets
+ {
private Dalamud dalamud;
+ private ClientStateAddressResolver address;
- public Actor CurrentTarget => GetActorByOffset(TargetOffsets.CurrentTarget);
- public Actor MouseOverTarget => GetActorByOffset(TargetOffsets.MouseOverTarget);
- public Actor FocusTarget => GetActorByOffset(TargetOffsets.FocusTarget);
- public Actor PreviousTarget => GetActorByOffset(TargetOffsets.PreviousTarget);
- public Actor SoftTarget => GetActorByOffset(TargetOffsets.SoftTarget);
-
- internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Dalamud instance.
+ /// The ClientStateAddressResolver instance.
+ internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
+ {
this.dalamud = dalamud;
- Address = addressResolver;
+ this.address = addressResolver;
}
- public void SetCurrentTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
- public void SetCurrentTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.CurrentTarget);
+ ///
+ /// Gets the current target.
+ ///
+ public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
- public void SetFocusTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
- public void SetFocusTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.FocusTarget);
+ ///
+ /// Gets the mouseover target.
+ ///
+ public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
- public void ClearCurrentTarget() => SetCurrentTarget(IntPtr.Zero);
- public void ClearFocusTarget() => SetFocusTarget(IntPtr.Zero);
+ ///
+ /// Gets the focus target.
+ ///
+ public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
- private void SetTarget(IntPtr actorAddress, int offset) {
- if (Address.TargetManager == IntPtr.Zero) return;
- Marshal.WriteIntPtr(Address.TargetManager, offset, actorAddress);
+ ///
+ /// Gets the previous target.
+ ///
+ public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget);
+
+ ///
+ /// Gets the soft target.
+ ///
+ public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget);
+
+ ///
+ /// Sets the current target.
+ ///
+ /// Actor to target.
+ public void SetCurrentTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
+
+ ///
+ /// Sets the current target.
+ ///
+ /// Actor (address) to target.
+ public void SetCurrentTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.CurrentTarget);
+
+ ///
+ /// Sets the focus target.
+ ///
+ /// Actor to focus.
+ public void SetFocusTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
+
+ ///
+ /// Sets the focus target.
+ ///
+ /// Actor (address) to focus.
+ public void SetFocusTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.FocusTarget);
+
+ ///
+ /// Clears the current target.
+ ///
+ public void ClearCurrentTarget() => this.SetCurrentTarget(IntPtr.Zero);
+
+ ///
+ /// Clears the focus target.
+ ///
+ public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
+
+ private void SetTarget(IntPtr actorAddress, int offset)
+ {
+ if (this.address.TargetManager == IntPtr.Zero)
+ return;
+
+ Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
}
-
- private Actor GetActorByOffset(int offset) {
- if (Address.TargetManager == IntPtr.Zero) return null;
- var actorAddress = Marshal.ReadIntPtr(Address.TargetManager + offset);
- if (actorAddress == IntPtr.Zero) return null;
+
+ private Actor GetActorByOffset(int offset)
+ {
+ if (this.address.TargetManager == IntPtr.Zero)
+ return null;
+
+ var actorAddress = Marshal.ReadIntPtr(this.address.TargetManager + offset);
+ if (actorAddress == IntPtr.Zero)
+ return null;
+
return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress);
}
}
+
+ ///
+ /// Memory offsets for the type.
+ ///
+ public static class TargetOffsets
+ {
+ public const int CurrentTarget = 0x80;
+ public const int SoftTarget = 0x88;
+ public const int MouseOverTarget = 0xD0;
+ public const int FocusTarget = 0xF8;
+ public const int PreviousTarget = 0x110;
+ }
}
diff --git a/Dalamud/Game/ClientState/Actors/Types/Actor.cs b/Dalamud/Game/ClientState/Actors/Types/Actor.cs
index 0c095bfb4..d971a4e1e 100644
--- a/Dalamud/Game/ClientState/Actors/Types/Actor.cs
+++ b/Dalamud/Game/ClientState/Actors/Types/Actor.cs
@@ -5,13 +5,11 @@ using Dalamud.Game.ClientState.Structs;
namespace Dalamud.Game.ClientState.Actors.Types
{
///
- /// This class represents a basic FFXIV actor.
+ /// This class represents a basic FFXIV actor.
///
public class Actor : IEquatable
{
private readonly Structs.Actor actorStruct;
- // This is a breaking change. StyleCop demands it.
- // private readonly IntPtr address;
private readonly Dalamud dalamud;
///
@@ -83,8 +81,6 @@ namespace Dalamud.Game.ClientState.Actors.Types
///
/// Gets the address of this actor in memory.
///
- // TODO: This is a breaking change, StyleCop demands it.
- // public IntPtr Address => this.address;
public readonly IntPtr Address;
///
diff --git a/Dalamud/Game/ClientState/Actors/Types/Chara.cs b/Dalamud/Game/ClientState/Actors/Types/Chara.cs
index 1a75b37e1..01e4ba243 100644
--- a/Dalamud/Game/ClientState/Actors/Types/Chara.cs
+++ b/Dalamud/Game/ClientState/Actors/Types/Chara.cs
@@ -29,7 +29,7 @@ namespace Dalamud.Game.ClientState.Actors.Types
///
/// Gets the ClassJob of this Chara.
///
- public ClassJob ClassJob => new ClassJob(this.ActorStruct.ClassJob, this.Dalamud);
+ public ClassJob ClassJob => new(this.ActorStruct.ClassJob, this.Dalamud);
///
/// Gets the current HP of this Chara.
diff --git a/Dalamud/Game/ClientState/Actors/Types/PartyMember.cs b/Dalamud/Game/ClientState/Actors/Types/PartyMember.cs
index 0465cebf1..74cf9c97c 100644
--- a/Dalamud/Game/ClientState/Actors/Types/PartyMember.cs
+++ b/Dalamud/Game/ClientState/Actors/Types/PartyMember.cs
@@ -2,27 +2,51 @@ using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Actors.Types
{
+ ///
+ /// This class represents a party member.
+ ///
public class PartyMember
{
+ ///
+ /// The name of the character.
+ ///
public string CharacterName;
+
+ ///
+ /// Unknown.
+ ///
public long Unknown;
+
+ ///
+ /// The actor object that corresponds to this party member.
+ ///
public Actor Actor;
+
+ ///
+ /// The kind or type of actor.
+ ///
public ObjectKind ObjectKind;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ActorTable instance.
+ /// The interop data struct.
public PartyMember(ActorTable table, Structs.PartyMember rawData)
{
- CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
- Unknown = rawData.unknown;
- Actor = null;
+ this.CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
+ this.Unknown = rawData.unknown;
+ this.Actor = null;
for (var i = 0; i < table.Length; i++)
{
if (table[i] != null && table[i].ActorId == rawData.actorId)
{
- Actor = table[i];
+ this.Actor = table[i];
break;
}
}
- ObjectKind = rawData.objectKind;
+
+ this.ObjectKind = rawData.objectKind;
}
}
}
diff --git a/Dalamud/Game/ClientState/Actors/Types/PlayerCharacter.cs b/Dalamud/Game/ClientState/Actors/Types/PlayerCharacter.cs
index 976503fec..7cc932550 100644
--- a/Dalamud/Game/ClientState/Actors/Types/PlayerCharacter.cs
+++ b/Dalamud/Game/ClientState/Actors/Types/PlayerCharacter.cs
@@ -31,12 +31,12 @@ namespace Dalamud.Game.ClientState.Actors.Types
///
/// Gets the current world of the character.
///
- public World CurrentWorld => new World(this.ActorStruct.CurrentWorld, this.Dalamud);
+ public World CurrentWorld => new(this.ActorStruct.CurrentWorld, this.Dalamud);
///
/// Gets the home world of the character.
///
- public World HomeWorld => new World(this.ActorStruct.HomeWorld, this.Dalamud);
+ public World HomeWorld => new(this.ActorStruct.HomeWorld, this.Dalamud);
///
/// Gets the Free Company tag of this player.
diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs
index f5215cb1c..2134ec4bc 100644
--- a/Dalamud/Game/ClientState/ClientState.cs
+++ b/Dalamud/Game/ClientState/ClientState.cs
@@ -1,10 +1,10 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
+
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.Internal;
-using Dalamud.Game.Internal.Network;
using Dalamud.Hooking;
using JetBrains.Annotations;
using Lumina.Excel.GeneratedSheets;
@@ -15,41 +15,17 @@ namespace Dalamud.Game.ClientState
///
/// This class represents the state of the game client at the time of access.
///
- public class ClientState : INotifyPropertyChanged, IDisposable {
- private readonly Dalamud dalamud;
- public event PropertyChangedEventHandler PropertyChanged;
-
- private ClientStateAddressResolver Address { get; }
-
- public readonly ClientLanguage ClientLanguage;
-
+ public class ClientState : INotifyPropertyChanged, IDisposable
+ {
///
/// The table of all present actors.
///
public readonly ActorTable Actors;
///
- /// The local player character, if one is present.
+ /// Gets the language of the client.
///
- [CanBeNull]
- public PlayerCharacter LocalPlayer {
- get {
- var actor = this.Actors[0];
-
- if (actor is PlayerCharacter pc)
- return pc;
-
- return null;
- }
- }
-
- #region TerritoryType
-
- // TODO: The hooking logic for this should go into a separate class.
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
-
- private readonly Hook setupTerritoryTypeHook;
+ public readonly ClientLanguage ClientLanguage;
///
/// The current Territory the player resides in.
@@ -57,39 +33,12 @@ namespace Dalamud.Game.ClientState
public ushort TerritoryType;
///
- /// Event that gets fired when the current Territory changes.
- ///
- public EventHandler TerritoryChanged;
-
- ///
- /// Event that gets fired when a duty is ready.
- ///
- public event EventHandler CfPop;
-
- private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
- {
- this.TerritoryType = terriType;
- this.TerritoryChanged?.Invoke(this, terriType);
-
- Log.Debug("TerritoryType changed: {0}", terriType);
-
- return this.setupTerritoryTypeHook.Original(manager, terriType);
- }
-
- #endregion
-
- ///
- /// The content ID of the local character.
- ///
- public ulong LocalContentId => (ulong) Marshal.ReadInt64(Address.LocalContentId);
-
- ///
- /// The class facilitating Job Gauge data access
+ /// The class facilitating Job Gauge data access.
///
public JobGauges JobGauges;
///
- /// The class facilitating party list data access
+ /// The class facilitating party list data access.
///
public PartyList PartyList;
@@ -102,77 +51,76 @@ namespace Dalamud.Game.ClientState
/// Provides access to the button state of gamepad buttons in game.
///
public GamepadState GamepadState;
-
+
///
/// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
///
public Condition Condition;
///
- /// The class facilitating target data access
+ /// The class facilitating target data access.
///
public Targets Targets;
///
+ /// Event that gets fired when the current Territory changes.
+ ///
+ public EventHandler TerritoryChanged;
+
+ private readonly Dalamud dalamud;
+ private readonly ClientStateAddressResolver address;
+ private readonly Hook setupTerritoryTypeHook;
+
+ private bool lastConditionNone = true;
+
+ ///
+ /// Initializes a new instance of the class.
/// Set up client state access.
///
- /// Dalamud instance
- /// /// StartInfo of the current Dalamud launch
- /// Sig scanner
- public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner) {
+ /// Dalamud instance.
+ /// StartInfo of the current Dalamud launch.
+ /// Sig scanner.
+ public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner)
+ {
this.dalamud = dalamud;
- Address = new ClientStateAddressResolver();
- Address.Setup(scanner);
+ this.address = new ClientStateAddressResolver();
+ this.address.Setup(scanner);
Log.Verbose("===== C L I E N T S T A T E =====");
this.ClientLanguage = startInfo.Language;
- this.Actors = new ActorTable(dalamud, Address);
+ this.Actors = new ActorTable(dalamud, this.address);
- this.PartyList = new PartyList(dalamud, Address);
+ this.PartyList = new PartyList(dalamud, this.address);
- this.JobGauges = new JobGauges(Address);
+ this.JobGauges = new JobGauges(this.address);
- this.KeyState = new KeyState(Address, scanner.Module.BaseAddress);
+ this.KeyState = new KeyState(this.address, scanner.Module.BaseAddress);
- this.GamepadState = new GamepadState(this.Address);
+ this.GamepadState = new GamepadState(this.address);
- this.Condition = new Condition( Address );
+ this.Condition = new Condition(this.address);
- this.Targets = new Targets(dalamud, Address);
+ this.Targets = new Targets(dalamud, this.address);
- Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", Address.SetupTerritoryType);
+ Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", this.address.SetupTerritoryType);
- this.setupTerritoryTypeHook = new Hook(Address.SetupTerritoryType,
- new SetupTerritoryTypeDelegate(SetupTerritoryTypeDetour),
- this);
+ this.setupTerritoryTypeHook = new Hook(this.address.SetupTerritoryType, new SetupTerritoryTypeDelegate(this.SetupTerritoryTypeDetour), this);
- dalamud.Framework.OnUpdateEvent += FrameworkOnOnUpdateEvent;
- dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
+ dalamud.Framework.OnUpdateEvent += this.FrameworkOnOnUpdateEvent;
+ dalamud.NetworkHandlers.CfPop += this.NetworkHandlersOnCfPop;
}
- private void NetworkHandlersOnCfPop(object sender, ContentFinderCondition e) {
- CfPop?.Invoke(this, e);
- }
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
- public void Enable() {
- this.GamepadState.Enable();
- this.PartyList.Enable();
- this.setupTerritoryTypeHook.Enable();
- }
-
- public void Dispose() {
- this.PartyList.Dispose();
- this.setupTerritoryTypeHook.Dispose();
- this.Actors.Dispose();
- this.GamepadState.Dispose();
-
- this.dalamud.Framework.OnUpdateEvent -= FrameworkOnOnUpdateEvent;
- this.dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
- }
-
- private bool lastConditionNone = true;
+ ///
+ /// Event that fires when a property changes.
+ ///
+#pragma warning disable CS0067
+ public event PropertyChangedEventHandler PropertyChanged;
+#pragma warning restore
///
/// Event that fires when a character is logging in.
@@ -184,24 +132,93 @@ namespace Dalamud.Game.ClientState
///
public event EventHandler OnLogout;
+ ///
+ /// Event that gets fired when a duty is ready.
+ ///
+ public event EventHandler CfPop;
+
+ ///
+ /// Gets the local player character, if one is present.
+ ///
+ [CanBeNull]
+ public PlayerCharacter LocalPlayer
+ {
+ get
+ {
+ var actor = this.Actors[0];
+
+ if (actor is PlayerCharacter pc)
+ return pc;
+
+ return null;
+ }
+ }
+
+ ///
+ /// Gets the content ID of the local character.
+ ///
+ public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
+
///
/// Gets a value indicating whether a character is logged in.
///
public bool IsLoggedIn { get; private set; }
- private void FrameworkOnOnUpdateEvent(Framework framework) {
- if (this.Condition.Any() && this.lastConditionNone == true) {
+ ///
+ /// Enable this module.
+ ///
+ public void Enable()
+ {
+ this.GamepadState.Enable();
+ this.PartyList.Enable();
+ this.setupTerritoryTypeHook.Enable();
+ }
+
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ this.PartyList.Dispose();
+ this.setupTerritoryTypeHook.Dispose();
+ this.Actors.Dispose();
+ this.GamepadState.Dispose();
+
+ this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;
+ this.dalamud.NetworkHandlers.CfPop += this.NetworkHandlersOnCfPop;
+ }
+
+ private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
+ {
+ this.TerritoryType = terriType;
+ this.TerritoryChanged?.Invoke(this, terriType);
+
+ Log.Debug("TerritoryType changed: {0}", terriType);
+
+ return this.setupTerritoryTypeHook.Original(manager, terriType);
+ }
+
+ private void NetworkHandlersOnCfPop(object sender, ContentFinderCondition e)
+ {
+ this.CfPop?.Invoke(this, e);
+ }
+
+ private void FrameworkOnOnUpdateEvent(Framework framework)
+ {
+ if (this.Condition.Any() && this.lastConditionNone == true)
+ {
Log.Debug("Is login");
this.lastConditionNone = false;
this.IsLoggedIn = true;
- OnLogin?.Invoke(this, null);
+ this.OnLogin?.Invoke(this, null);
}
-
- if (!this.Condition.Any() && this.lastConditionNone == false) {
+
+ if (!this.Condition.Any() && this.lastConditionNone == false)
+ {
Log.Debug("Is logout");
this.lastConditionNone = true;
this.IsLoggedIn = false;
- OnLogout?.Invoke(this, null);
+ this.OnLogout?.Invoke(this, null);
}
}
}
diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
index 94591bf4e..ed87f06d5 100644
--- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
+++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
@@ -1,50 +1,88 @@
using System;
+
using Dalamud.Game.Internal;
namespace Dalamud.Game.ClientState
{
- public sealed class ClientStateAddressResolver : BaseAddressResolver {
+ ///
+ /// Client state memory address resolver.
+ ///
+ public sealed class ClientStateAddressResolver : BaseAddressResolver
+ {
// Static offsets
- public IntPtr ActorTable { get; private set; }
- //public IntPtr ViewportActorTable { get; private set; }
- public IntPtr LocalContentId { get; private set; }
- public IntPtr JobGaugeData { get; private set; }
- public IntPtr KeyboardState { get; private set; }
- public IntPtr TargetManager { get; private set; }
-
- // Functions
- public IntPtr SetupTerritoryType { get; private set; }
- //public IntPtr SomeActorTableAccess { get; private set; }
- //public IntPtr PartyListUpdate { get; private set; }
///
- /// Game function which polls the gamepads for data.
- ///
+ /// Gets the address of the actor table.
+ ///
+ public IntPtr ActorTable { get; private set; }
+
+ // public IntPtr ViewportActorTable { get; private set; }
+
+ ///
+ /// Gets the address of the local content id.
+ ///
+ public IntPtr LocalContentId { get; private set; }
+
+ ///
+ /// Gets the address of job gauge data.
+ ///
+ public IntPtr JobGaugeData { get; private set; }
+
+ ///
+ /// Gets the address of the keyboard state.
+ ///
+ public IntPtr KeyboardState { get; private set; }
+
+ ///
+ /// Gets the address of the target manager.
+ ///
+ public IntPtr TargetManager { get; private set; }
+
+ ///
+ /// Gets the address of the condition flag array.
+ ///
+ public IntPtr ConditionFlags { get; private set; }
+
+ // Functions
+
+ ///
+ /// Gets the address of the method which sets the territory type.
+ ///
+ public IntPtr SetupTerritoryType { get; private set; }
+
+ // public IntPtr SomeActorTableAccess { get; private set; }
+ // public IntPtr PartyListUpdate { get; private set; }
+
+ ///
+ /// Gets the address of the method which polls the gamepads for data.
/// Called every frame, even when `Enable Gamepad` is off in the settings.
///
public IntPtr GamepadPoll { get; private set; }
- public IntPtr ConditionFlags { get; private set; }
-
- protected override void Setup64Bit(SigScanner sig) {
+ ///
+ /// Scan for and setup any configured address pointers.
+ ///
+ /// The signature scanner to facilitate setup.
+ protected override void Setup64Bit(SigScanner sig)
+ {
// We don't need those anymore, but maybe someone else will - let's leave them here for good measure
- //ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
- //SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
- ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
+ // ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
+ // SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
+ this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
- LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
- JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
+ this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
+ this.JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
- SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
+ this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
// This resolves to a fixed offset only, without the base address added in, so GetStaticAddressFromSig() can't be used
- KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
+ this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
- //PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
+ // PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
- ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
+ this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
- TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
+ this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
}
diff --git a/Dalamud/Game/ClientState/Condition.cs b/Dalamud/Game/ClientState/Condition.cs
index 9b1e42c29..b416c4df4 100644
--- a/Dalamud/Game/ClientState/Condition.cs
+++ b/Dalamud/Game/ClientState/Condition.cs
@@ -1,7 +1,4 @@
using System;
-using System.Runtime.CompilerServices;
-using Dalamud.Hooking;
-using Serilog;
namespace Dalamud.Game.ClientState
{
@@ -10,36 +7,49 @@ namespace Dalamud.Game.ClientState
///
public class Condition
{
- internal readonly IntPtr conditionArrayBase;
-
///
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
///
public const int MaxConditionEntries = 100;
- internal Condition( ClientStateAddressResolver resolver )
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ClientStateAddressResolver instance.
+ internal Condition(ClientStateAddressResolver resolver)
{
- this.conditionArrayBase = resolver.ConditionFlags;
+ this.ConditionArrayBase = resolver.ConditionFlags;
}
+ ///
+ /// Gets the condition array base pointer.
+ /// Would typically be private but is used in /xldata windows.
+ ///
+ internal IntPtr ConditionArrayBase { get; private set; }
+
///
/// Check the value of a specific condition/state flag.
///
- /// The condition flag to check
- public unsafe bool this[ ConditionFlag flag ]
+ /// The condition flag to check.
+ public unsafe bool this[ConditionFlag flag]
{
get
{
- var idx = ( int )flag;
-
- if( idx > MaxConditionEntries || idx < 0 )
+ var idx = (int)flag;
+
+ if (idx > MaxConditionEntries || idx < 0)
return false;
-
- return *( bool* )( this.conditionArrayBase + idx );
+
+ return *(bool*)(this.ConditionArrayBase + idx);
}
}
- public bool Any() {
+ ///
+ /// Check if any condition flags are set.
+ ///
+ /// Whether any single flag is set.
+ public bool Any()
+ {
for (var i = 0; i < MaxConditionEntries; i++)
{
var typedCondition = (ConditionFlag)i;
diff --git a/Dalamud/Game/ClientState/ConditionFlag.cs b/Dalamud/Game/ClientState/ConditionFlag.cs
index f7c866a96..dc3d72d61 100644
--- a/Dalamud/Game/ClientState/ConditionFlag.cs
+++ b/Dalamud/Game/ClientState/ConditionFlag.cs
@@ -6,7 +6,8 @@ namespace Dalamud.Game.ClientState
/// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to
/// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to.
///
- public enum ConditionFlag {
+ public enum ConditionFlag
+ {
///
/// Unused.
///
@@ -27,9 +28,6 @@ namespace Dalamud.Game.ClientState
///
Emoting = 3,
- ///
- /// Unable to execute command while mounted.
- ///
///
/// Unable to execute command while mounted.
///
@@ -94,15 +92,15 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while performing.
///
Performing = 16,
-
- //Unknown17 = 17,
- //Unknown18 = 18,
- //Unknown19 = 19,
- //Unknown20 = 20,
- //Unknown21 = 21,
- //Unknown22 = 22,
- //Unknown23 = 23,
- //Unknown24 = 24,
+
+ // Unknown17 = 17,
+ // Unknown18 = 18,
+ // Unknown19 = 19,
+ // Unknown20 = 20,
+ // Unknown21 = 21,
+ // Unknown22 = 22,
+ // Unknown23 = 23,
+ // Unknown24 = 24,
///
/// Unable to execute command while occupied.
@@ -199,8 +197,8 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while fishing.
///
Fishing = 43,
-
- //Unknown44 = 44,
+
+ // Unknown44 = 44,
///
/// Unable to execute command while between areas.
@@ -211,8 +209,8 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while stealthed.
///
Stealthed = 46,
-
- //Unknown47 = 47,
+
+ // Unknown47 = 47,
///
/// Unable to execute command while jumping.
@@ -399,8 +397,8 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while participating in a cross-world party or alliance.
///
ParticipatingInCrossWorldPartyOrAlliance = 84,
-
- //Unknown85 = 85,
+
+ // Unknown85 = 85,
///
/// Unable to execute command while playing duty record.
diff --git a/Dalamud/Game/ClientState/GamepadState.cs b/Dalamud/Game/ClientState/GamepadState.cs
index b9711d093..e3ffc1cef 100644
--- a/Dalamud/Game/ClientState/GamepadState.cs
+++ b/Dalamud/Game/ClientState/GamepadState.cs
@@ -126,7 +126,7 @@ namespace Dalamud.Game.ClientState
/// Gets or sets a value indicating whether detour should block gamepad input for game.
///
/// Ideally, we would use
- /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
+ /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
/// but this has a race condition during load with the detour which sets up ImGui
/// and throws if our detour gets called before the other.
///
diff --git a/Dalamud/Game/ClientState/KeyState.cs b/Dalamud/Game/ClientState/KeyState.cs
index 5ae92c20c..8bc3c200f 100644
--- a/Dalamud/Game/ClientState/KeyState.cs
+++ b/Dalamud/Game/ClientState/KeyState.cs
@@ -1,22 +1,26 @@
-using Serilog;
using System;
using System.Runtime.InteropServices;
+using Serilog;
+
namespace Dalamud.Game.ClientState
{
///
- /// Wrapper around the game keystate buffer, which contains the pressed state for
- /// all keyboard keys, indexed by virtual vkCode
+ /// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
///
public class KeyState
{
- private IntPtr bufferBase;
-
// The array is accessed in a way that this limit doesn't appear to exist
// but there is other state data past this point, and keys beyond here aren't
// generally valid for most things anyway
private const int MaxKeyCodeIndex = 0xA0;
+ private IntPtr bufferBase;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ClientStateAddressResolver instance.
+ /// The base address of the main process module.
public KeyState(ClientStateAddressResolver addressResolver, IntPtr moduleBaseAddress)
{
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
@@ -33,10 +37,10 @@ namespace Dalamud.Game.ClientState
{
get
{
- if (vkCode< 0 || vkCode > MaxKeyCodeIndex)
+ if (vkCode < 0 || vkCode > MaxKeyCodeIndex)
throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}");
- return (Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0);
+ return Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0;
}
set
diff --git a/Dalamud/Game/ClientState/PartyList.cs b/Dalamud/Game/ClientState/PartyList.cs
index a34571c5a..a1db673af 100644
--- a/Dalamud/Game/ClientState/PartyList.cs
+++ b/Dalamud/Game/ClientState/PartyList.cs
@@ -1,96 +1,139 @@
-using Dalamud.Game.ClientState.Actors.Types;
-using Dalamud.Hooking;
-using Dalamud.Plugin;
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Linq;
using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
+
+using Dalamud.Game.ClientState.Actors.Types;
+// using Dalamud.Hooking;
namespace Dalamud.Game.ClientState
{
- public class PartyList : IReadOnlyCollection, ICollection, IDisposable
+ ///
+ /// This class represents the members of your party.
+ ///
+ public sealed partial class PartyList
{
- private ClientStateAddressResolver Address { get; }
- private Dalamud dalamud;
+ private readonly Dalamud dalamud;
+ private readonly ClientStateAddressResolver address;
+
+ // private bool isReady = false;
+ // private IntPtr partyListBegin;
+ // private Hook partyListUpdateHook;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Dalamud instance.
+ /// The ClientStateAddressResolver instance.
+ public PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
+ {
+ this.address = addressResolver;
+ this.dalamud = dalamud;
+ // this.partyListUpdateHook = new Hook(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
+ }
private delegate long PartyListUpdateDelegate(IntPtr structBegin, long param2, char param3);
- private Hook partyListUpdateHook;
- private IntPtr partyListBegin;
- private bool isReady = false;
+ ///
+ /// Gets the length of the PartyList.
+ ///
+ public int Length => 0; // !this.isReady ? 0 : Marshal.ReadByte(this.partyListBegin + 0xF0);
- public PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
+ ///
+ /// Get the nth party member.
+ ///
+ /// Index of the party member.
+ /// The party member.
+ public PartyMember this[int index]
{
- Address = addressResolver;
- this.dalamud = dalamud;
- //this.partyListUpdateHook = new Hook(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
+ get
+ {
+ return null;
+ // if (!this.isReady)
+ // return null;
+ // if (index >= this.Length)
+ // return null;
+ // var tblIndex = this.partyListBegin + (index * 24);
+ // var memberStruct = Marshal.PtrToStructure(tblIndex);
+ // return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
+ }
}
+ ///
+ /// Enable this module.
+ ///
public void Enable()
{
// TODO Fix for 5.3
- //this.partyListUpdateHook.Enable();
+ // this.partyListUpdateHook.Enable();
}
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
public void Dispose()
{
- //if (!this.isReady)
- // this.partyListUpdateHook.Dispose();
- this.isReady = false;
+ // if (!this.isReady)
+ // this.partyListUpdateHook.Dispose();
+ // this.isReady = false;
}
- private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
- {
- var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
- this.partyListBegin = structBegin + 0xB48;
- this.partyListUpdateHook.Dispose();
- this.isReady = true;
- return result;
- }
+ // private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
+ // {
+ // var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
+ // this.partyListBegin = structBegin + 0xB48;
+ // this.partyListUpdateHook.Dispose();
+ // this.isReady = true;
+ // return result;
+ // }
+ }
- public PartyMember this[int index]
- {
- get {
- if (!this.isReady)
- return null;
- if (index >= Length)
- return null;
- var tblIndex = partyListBegin + index * 24;
- var memberStruct = Marshal.PtrToStructure(tblIndex);
- return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
- }
- }
+ ///
+ /// Implements IReadOnlyCollection, IEnumerable.
+ ///
+ public sealed partial class PartyList : IReadOnlyCollection
+ {
+ ///
+ int IReadOnlyCollection.Count => this.Length;
- public void CopyTo(Array array, int index)
+ ///
+ public IEnumerator GetEnumerator()
{
- for (var i = 0; i < Length; i++)
+ for (var i = 0; i < this.Length; i++)
{
- array.SetValue(this[i], index);
- index++;
- }
- }
-
- public IEnumerator GetEnumerator() {
- for (var i = 0; i < Length; i++) {
- if (this[i] != null) {
+ if (this[i] != null)
+ {
yield return this[i];
}
}
}
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ }
- public int Length => !this.isReady ? 0 : Marshal.ReadByte(partyListBegin + 0xF0);
-
- int IReadOnlyCollection.Count => Length;
-
- public int Count => Length;
+ ///
+ /// Implements ICollection.
+ ///
+ public sealed partial class PartyList : ICollection
+ {
+ ///
+ public int Count => this.Length;
+ ///
public object SyncRoot => this;
+ ///
public bool IsSynchronized => false;
+
+ ///
+ public void CopyTo(Array array, int index)
+ {
+ for (var i = 0; i < this.Length; i++)
+ {
+ array.SetValue(this[i], index);
+ index++;
+ }
+ }
}
}
diff --git a/Dalamud/Game/ClientState/Structs/Actor.cs b/Dalamud/Game/ClientState/Structs/Actor.cs
index 85c78ab6b..e2693d6c3 100644
--- a/Dalamud/Game/ClientState/Structs/Actor.cs
+++ b/Dalamud/Game/ClientState/Structs/Actor.cs
@@ -4,7 +4,245 @@ using Dalamud.Game.ClientState.Actors;
namespace Dalamud.Game.ClientState.Structs
{
- public class ActorOffsets
+ ///
+ /// Native memory representation of an FFXIV actor.
+ ///
+ [StructLayout(LayoutKind.Explicit, Pack = 2)]
+ public struct Actor
+ {
+ ///
+ /// The actor name.
+ ///
+ [FieldOffset(ActorOffsets.Name)]
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
+ public string Name;
+
+ ///
+ /// The actor's internal id.
+ ///
+ [FieldOffset(ActorOffsets.ActorId)]
+ public int ActorId;
+
+ ///
+ /// The actor's data id.
+ ///
+ [FieldOffset(ActorOffsets.DataId)]
+ public int DataId;
+
+ ///
+ /// The actor's owner id. This is useful for pets, summons, and the like.
+ ///
+ [FieldOffset(ActorOffsets.OwnerId)]
+ public int OwnerId;
+
+ ///
+ /// The type or kind of actor.
+ ///
+ [FieldOffset(ActorOffsets.ObjectKind)]
+ public ObjectKind ObjectKind;
+
+ ///
+ /// The sub-type or sub-kind of actor.
+ ///
+ [FieldOffset(ActorOffsets.SubKind)]
+ public byte SubKind;
+
+ ///
+ /// Whether the actor is friendly.
+ ///
+ [FieldOffset(ActorOffsets.IsFriendly)]
+ public bool IsFriendly;
+
+ ///
+ /// The horizontal distance in game units from the player.
+ ///
+ [FieldOffset(ActorOffsets.YalmDistanceFromPlayerX)]
+ public byte YalmDistanceFromPlayerX;
+
+ ///
+ /// The player target status.
+ ///
+ ///
+ /// This is some kind of enum.
+ ///
+ [FieldOffset(ActorOffsets.PlayerTargetStatus)]
+ public byte PlayerTargetStatus;
+
+ ///
+ /// The vertical distance in game units from the player.
+ ///
+ [FieldOffset(ActorOffsets.YalmDistanceFromPlayerY)]
+ public byte YalmDistanceFromPlayerY;
+
+ ///
+ /// The (X,Z,Y) position of the actor.
+ ///
+ [FieldOffset(ActorOffsets.Position)]
+ public Position3 Position;
+
+ ///
+ /// The rotation of the actor.
+ ///
+ ///
+ /// The rotation is around the vertical axis (yaw), from -pi to pi radians.
+ ///
+ [FieldOffset(ActorOffsets.Rotation)]
+ public float Rotation;
+
+ ///
+ /// The hitbox radius of the actor.
+ ///
+ [FieldOffset(ActorOffsets.HitboxRadius)]
+ public float HitboxRadius;
+
+ ///
+ /// The current HP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentHp)]
+ public int CurrentHp;
+
+ ///
+ /// The max HP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.MaxHp)]
+ public int MaxHp;
+
+ ///
+ /// The current MP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentMp)]
+ public int CurrentMp;
+
+ ///
+ /// The max MP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.MaxMp)]
+ public short MaxMp;
+
+ ///
+ /// The current GP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentGp)]
+ public short CurrentGp;
+
+ ///
+ /// The max GP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.MaxGp)]
+ public short MaxGp;
+
+ ///
+ /// The current CP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentCp)]
+ public short CurrentCp;
+
+ ///
+ /// The max CP of the actor.
+ ///
+ [FieldOffset(ActorOffsets.MaxCp)]
+ public short MaxCp;
+
+ ///
+ /// The class-job of the actor.
+ ///
+ [FieldOffset(ActorOffsets.ClassJob)]
+ public byte ClassJob;
+
+ ///
+ /// The level of the actor.
+ ///
+ [FieldOffset(ActorOffsets.Level)]
+ public byte Level;
+
+ ///
+ /// The (player character) actor ID being targeted by the actor.
+ ///
+ [FieldOffset(ActorOffsets.PlayerCharacterTargetActorId)]
+ public int PlayerCharacterTargetActorId;
+
+ ///
+ /// The customization byte/bitfield of the actor.
+ ///
+ [FieldOffset(ActorOffsets.Customize)]
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
+ public byte[] Customize;
+
+ // Normally pack=2 should work, but ByTVal or Injection breaks this.
+ // [FieldOffset(ActorOffsets.CompanyTag)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public string CompanyTag;
+
+ ///
+ /// The (battle npc) actor ID being targeted by the actor.
+ ///
+ [FieldOffset(ActorOffsets.BattleNpcTargetActorId)]
+ public int BattleNpcTargetActorId;
+
+ ///
+ /// The name ID of the actor.
+ ///
+ [FieldOffset(ActorOffsets.NameId)]
+ public int NameId;
+
+ ///
+ /// The current world ID of the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentWorld)]
+ public ushort CurrentWorld;
+
+ ///
+ /// The home world ID of the actor.
+ ///
+ [FieldOffset(ActorOffsets.HomeWorld)]
+ public ushort HomeWorld;
+
+ ///
+ /// Whether the actor is currently casting.
+ ///
+ [FieldOffset(ActorOffsets.IsCasting)]
+ public bool IsCasting;
+
+ ///
+ /// Whether the actor is currently casting (dup?).
+ ///
+ [FieldOffset(ActorOffsets.IsCasting2)]
+ public bool IsCasting2;
+
+ ///
+ /// The spell action ID currently being cast by the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentCastSpellActionId)]
+ public uint CurrentCastSpellActionId;
+
+ ///
+ /// The actor ID of the target currently being cast at by the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentCastTargetActorId)]
+ public uint CurrentCastTargetActorId;
+
+ ///
+ /// The current casting time of the spell being cast by the actor.
+ ///
+ [FieldOffset(ActorOffsets.CurrentCastTime)]
+ public float CurrentCastTime;
+
+ ///
+ /// The total casting time of the spell being cast by the actor.
+ ///
+ [FieldOffset(ActorOffsets.TotalCastTime)]
+ public float TotalCastTime;
+
+ ///
+ /// The array of status effects that the actor is currently affected by.
+ ///
+ [FieldOffset(ActorOffsets.UIStatusEffects)]
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
+ public StatusEffect[] UIStatusEffects;
+ }
+
+ ///
+ /// Memory offsets for the type.
+ ///
+ public static class ActorOffsets
{
// Reference https://github.com/FFXIVAPP/sharlayan-resources/blob/master/structures/5.4/x64.json for more
public const int Name = 48; // 0x0030
@@ -48,51 +286,4 @@ namespace Dalamud.Game.ClientState.Structs
public const int TotalCastTime = 0x1BB8;
public const int UIStatusEffects = 0x19F8;
}
-
- ///
- /// Native memory representation of a FFXIV actor.
- ///
- [StructLayout(LayoutKind.Explicit, Pack = 2)]
- public struct Actor
- {
- [FieldOffset(ActorOffsets.Name)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)] public string Name;
- [FieldOffset(ActorOffsets.ActorId)] public int ActorId;
- [FieldOffset(ActorOffsets.DataId)] public int DataId;
- [FieldOffset(ActorOffsets.OwnerId)] public int OwnerId;
- [FieldOffset(ActorOffsets.ObjectKind)] public ObjectKind ObjectKind;
- [FieldOffset(ActorOffsets.SubKind)] public byte SubKind;
- [FieldOffset(ActorOffsets.IsFriendly)] public bool IsFriendly;
- [FieldOffset(ActorOffsets.YalmDistanceFromPlayerX)] public byte YalmDistanceFromPlayerX; // Demo says one of these is x distance
- [FieldOffset(ActorOffsets.PlayerTargetStatus)] public byte PlayerTargetStatus; // This is some kind of enum
- [FieldOffset(ActorOffsets.YalmDistanceFromPlayerY)] public byte YalmDistanceFromPlayerY; // and the other is z distance
- [FieldOffset(ActorOffsets.Position)] public Position3 Position;
- [FieldOffset(ActorOffsets.Rotation)] public float Rotation; // Rotation around the vertical axis (yaw), from -pi to pi radians
- [FieldOffset(ActorOffsets.HitboxRadius)] public float HitboxRadius;
- [FieldOffset(ActorOffsets.CurrentHp)] public int CurrentHp;
- [FieldOffset(ActorOffsets.MaxHp)] public int MaxHp;
- [FieldOffset(ActorOffsets.CurrentMp)] public int CurrentMp;
- [FieldOffset(ActorOffsets.MaxMp)] public short MaxMp;
- [FieldOffset(ActorOffsets.CurrentGp)] public short CurrentGp;
- [FieldOffset(ActorOffsets.MaxGp)] public short MaxGp;
- [FieldOffset(ActorOffsets.CurrentCp)] public short CurrentCp;
- [FieldOffset(ActorOffsets.MaxCp)] public short MaxCp;
- [FieldOffset(ActorOffsets.ClassJob)] public byte ClassJob;
- [FieldOffset(ActorOffsets.Level)] public byte Level;
- [FieldOffset(ActorOffsets.PlayerCharacterTargetActorId)] public int PlayerCharacterTargetActorId;
- [FieldOffset(ActorOffsets.Customize)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)] public byte[] Customize;
-
- // Normally pack=2 should work, but ByTVal or Injection breaks this.
- // [FieldOffset(ActorOffsets.CompanyTag)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public string CompanyTag;
- [FieldOffset(ActorOffsets.BattleNpcTargetActorId)] public int BattleNpcTargetActorId;
- [FieldOffset(ActorOffsets.NameId)] public int NameId;
- [FieldOffset(ActorOffsets.CurrentWorld)] public ushort CurrentWorld;
- [FieldOffset(ActorOffsets.HomeWorld)] public ushort HomeWorld;
- [FieldOffset(ActorOffsets.IsCasting)] public bool IsCasting;
- [FieldOffset(ActorOffsets.IsCasting2)] public bool IsCasting2;
- [FieldOffset(ActorOffsets.CurrentCastSpellActionId)] public uint CurrentCastSpellActionId;
- [FieldOffset(ActorOffsets.CurrentCastTargetActorId)] public uint CurrentCastTargetActorId;
- [FieldOffset(ActorOffsets.CurrentCastTime)] public float CurrentCastTime;
- [FieldOffset(ActorOffsets.TotalCastTime)] public float TotalCastTime;
- [FieldOffset(ActorOffsets.UIStatusEffects)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public StatusEffect[] UIStatusEffects;
- }
}
diff --git a/Dalamud/Game/ClientState/Structs/JobGauge/JobEnums.cs b/Dalamud/Game/ClientState/Structs/JobGauge/JobEnums.cs
index 4c6ab351a..dba4ba4a0 100644
--- a/Dalamud/Game/ClientState/Structs/JobGauge/JobEnums.cs
+++ b/Dalamud/Game/ClientState/Structs/JobGauge/JobEnums.cs
@@ -1,5 +1,7 @@
using System;
+#pragma warning disable SA1402 // File may only contain a single type
+
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
#region AST
@@ -270,3 +272,5 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
#endregion
}
+
+#pragma warning restore SA1402 // File may only contain a single type
diff --git a/Dalamud/Game/ClientState/Structs/PartyMember.cs b/Dalamud/Game/ClientState/Structs/PartyMember.cs
index 71dbf0d91..6acff52e7 100644
--- a/Dalamud/Game/ClientState/Structs/PartyMember.cs
+++ b/Dalamud/Game/ClientState/Structs/PartyMember.cs
@@ -1,19 +1,26 @@
-using Dalamud.Game.ClientState.Actors;
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
+
+using Dalamud.Game.ClientState.Actors;
namespace Dalamud.Game.ClientState.Structs
{
+ ///
+ /// This represents a native PartyMember class in memory.
+ ///
[StructLayout(LayoutKind.Explicit)]
public struct PartyMember
{
- [FieldOffset(0x0)] public IntPtr namePtr;
- [FieldOffset(0x8)] public long unknown;
- [FieldOffset(0x10)] public int actorId;
- [FieldOffset(0x14)] public ObjectKind objectKind;
+ [FieldOffset(0x0)]
+ public IntPtr namePtr;
+
+ [FieldOffset(0x8)]
+ public long unknown;
+
+ [FieldOffset(0x10)]
+ public int actorId;
+
+ [FieldOffset(0x14)]
+ public ObjectKind objectKind;
}
}
diff --git a/Dalamud/Game/ClientState/Structs/StatusEffect.cs b/Dalamud/Game/ClientState/Structs/StatusEffect.cs
index 584c5c48d..6d4c2fd77 100644
--- a/Dalamud/Game/ClientState/Structs/StatusEffect.cs
+++ b/Dalamud/Game/ClientState/Structs/StatusEffect.cs
@@ -1,4 +1,3 @@
-using System;
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs
@@ -9,10 +8,29 @@ namespace Dalamud.Game.ClientState.Structs
[StructLayout(LayoutKind.Sequential)]
public struct StatusEffect
{
+ ///
+ /// The effect ID.
+ ///
public short EffectId;
+
+ ///
+ /// How many stacks are present.
+ ///
public byte StackCount;
+
+ ///
+ /// Additional parameters.
+ ///
public byte Param;
+
+ ///
+ /// The duration remaining.
+ ///
public float Duration;
+
+ ///
+ /// The ID of the actor that caused this effect.
+ ///
public int OwnerId;
}
}
diff --git a/Dalamud/Game/Command/CommandInfo.cs b/Dalamud/Game/Command/CommandInfo.cs
index c735f78a8..0eb97177c 100644
--- a/Dalamud/Game/Command/CommandInfo.cs
+++ b/Dalamud/Game/Command/CommandInfo.cs
@@ -1,10 +1,23 @@
using System.Reflection;
-namespace Dalamud.Game.Command {
+namespace Dalamud.Game.Command
+{
///
/// This class describes a registered command.
///
- public sealed class CommandInfo {
+ public sealed class CommandInfo
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// Create a new CommandInfo with the provided handler.
+ ///
+ /// The method to call when the command is run.
+ public CommandInfo(HandlerDelegate handler)
+ {
+ this.Handler = handler;
+ this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
+ }
+
///
/// The function to be executed when the command is dispatched.
///
@@ -13,29 +26,23 @@ namespace Dalamud.Game.Command {
public delegate void HandlerDelegate(string command, string arguments);
///
- /// A which will be called when the command is dispatched.
+ /// Gets a which will be called when the command is dispatched.
///
public HandlerDelegate Handler { get; }
///
- /// The help message for this command.
+ /// Gets or sets the help message for this command.
///
public string HelpMessage { get; set; } = string.Empty;
///
- /// If this command should be shown in the help output.
+ /// Gets or sets a value indicating whether if this command should be shown in the help output.
///
public bool ShowInHelp { get; set; } = true;
-
- ///
- /// Create a new CommandInfo with the provided handler.
- ///
- ///
- public CommandInfo(HandlerDelegate handler) {
- Handler = handler;
- LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
- }
+ ///
+ /// Gets or sets the name of the assembly responsible for this command.
+ ///
internal string LoaderAssemblyName { get; set; } = string.Empty;
}
}
diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs
index 2cfc9daf2..9d7dac9cd 100644
--- a/Dalamud/Game/Command/CommandManager.cs
+++ b/Dalamud/Game/Command/CommandManager.cs
@@ -2,45 +2,37 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
+
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
-using Dalamud.Game.Internal.Libc;
using Serilog;
-namespace Dalamud.Game.Command {
+namespace Dalamud.Game.Command
+{
///
/// This class manages registered in-game slash commands.
///
- public sealed class CommandManager {
+ public sealed class CommandManager
+ {
private readonly Dalamud dalamud;
-
- private readonly Dictionary commandMap = new Dictionary();
-
- ///
- /// Read-only list of all registered commands.
- ///
- public ReadOnlyDictionary Commands =>
- new ReadOnlyDictionary(this.commandMap);
-
- private readonly Regex commandRegexEn =
- new Regex(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled);
-
- private readonly Regex commandRegexJp = new Regex(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled);
-
- private readonly Regex commandRegexDe =
- new Regex(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
-
- private readonly Regex commandRegexFr =
- new Regex(@"^La commande texte “(?.+)” n'existe pas\.$",
- RegexOptions.Compiled);
-
+ private readonly Dictionary commandMap = new();
+ private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled);
+ private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled);
+ private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
+ private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled);
private readonly Regex currentLangCommandRegex;
-
- public CommandManager(Dalamud dalamud, ClientLanguage language) {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Dalamud instance.
+ /// The client language requested.
+ public CommandManager(Dalamud dalamud, ClientLanguage language)
+ {
this.dalamud = dalamud;
- switch (language) {
+ switch (language)
+ {
case ClientLanguage.Japanese:
this.currentLangCommandRegex = this.commandRegexJp;
break;
@@ -55,40 +47,42 @@ namespace Dalamud.Game.Command {
break;
}
- dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
+ dalamud.Framework.Gui.Chat.OnCheckMessageHandled += this.OnCheckMessageHandled;
}
- private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
- if (type == XivChatType.ErrorMessage && senderId == 0) {
- var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
- if (cmdMatch.Success) {
- // Yes, it's a chat command.
- var command = cmdMatch.Value;
- if (ProcessCommand(command)) isHandled = true;
- }
- }
- }
+ ///
+ /// Gets a read-only list of all registered commands.
+ ///
+ public ReadOnlyDictionary Commands => new(this.commandMap);
///
/// Process a command in full.
///
/// The full command string.
/// True if the command was found and dispatched.
- public bool ProcessCommand(string content) {
+ public bool ProcessCommand(string content)
+ {
string command;
string argument;
var separatorPosition = content.IndexOf(' ');
- if (separatorPosition == -1 || separatorPosition + 1 >= content.Length) {
+ if (separatorPosition == -1 || separatorPosition + 1 >= content.Length)
+ {
// If no space was found or ends with the space. Process them as a no argument
- if (separatorPosition + 1 >= content.Length) {
+ if (separatorPosition + 1 >= content.Length)
+ {
// Remove the trailing space
command = content.Substring(0, separatorPosition);
- } else {
+ }
+ else
+ {
command = content;
}
+
argument = string.Empty;
- } else {
+ }
+ else
+ {
// e.g.)
// /testcommand arg1
// => Total of 17 chars
@@ -98,13 +92,13 @@ namespace Dalamud.Game.Command {
command = content.Substring(0, separatorPosition);
var argStart = separatorPosition + 1;
- argument = content.Substring(argStart, content.Length - argStart);
+ argument = content[argStart..];
}
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
return false;
- DispatchCommand(command, argument, handler);
+ this.DispatchCommand(command, argument, handler);
return true;
}
@@ -114,12 +108,15 @@ namespace Dalamud.Game.Command {
/// The command to dispatch.
/// The provided arguments.
/// A object describing this command.
- public void DispatchCommand(string command, string argument, CommandInfo info) {
- try {
+ public void DispatchCommand(string command, string argument, CommandInfo info)
+ {
+ try
+ {
info.Handler(command, argument);
- } catch (Exception ex) {
- Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command,
- argument);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
}
}
@@ -129,13 +126,17 @@ namespace Dalamud.Game.Command {
/// The command to register.
/// A object describing the command.
/// If adding was successful.
- public bool AddHandler(string command, CommandInfo info) {
+ public bool AddHandler(string command, CommandInfo info)
+ {
if (info == null) throw new ArgumentNullException(nameof(info), "Command handler is null.");
- try {
+ try
+ {
this.commandMap.Add(command, info);
return true;
- } catch (ArgumentException) {
+ }
+ catch (ArgumentException)
+ {
Log.Error("Command {CommandName} is already registered.", command);
return false;
}
@@ -146,8 +147,23 @@ namespace Dalamud.Game.Command {
///
/// The command to remove.
/// If the removal was successful.
- public bool RemoveHandler(string command) {
+ public bool RemoveHandler(string command)
+ {
return this.commandMap.Remove(command);
}
+
+ private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
+ {
+ if (type == XivChatType.ErrorMessage && senderId == 0)
+ {
+ var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
+ if (cmdMatch.Success)
+ {
+ // Yes, it's a chat command.
+ var command = cmdMatch.Value;
+ if (this.ProcessCommand(command)) isHandled = true;
+ }
+ }
+ }
}
}
diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs
index 0e76f316a..04b107458 100644
--- a/Dalamud/Game/Internal/AntiDebug.cs
+++ b/Dalamud/Game/Internal/AntiDebug.cs
@@ -1,48 +1,118 @@
using System;
using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using Dalamud.Hooking;
-using EasyHook;
+
using Serilog;
namespace Dalamud.Game.Internal
{
- public class AntiDebug : IDisposable
+ ///
+ /// This class disables anti-debug functionality in the game client.
+ ///
+ public sealed partial class AntiDebug
{
- private IntPtr DebugCheckAddress { get; set; }
-
- public bool IsEnabled { get; private set; }
-
- public AntiDebug(SigScanner scanner) {
- try {
- DebugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41");
- } catch (KeyNotFoundException) {
- DebugCheckAddress = IntPtr.Zero;
- }
-
- Log.Verbose("DebugCheck address {DebugCheckAddress}", DebugCheckAddress);
- }
-
private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
private byte[] original;
+ private IntPtr debugCheckAddress;
- public void Enable() {
- this.original = new byte[this.nop.Length];
- if (DebugCheckAddress != IntPtr.Zero && !IsEnabled) {
- Log.Information($"Overwriting Debug Check @ 0x{DebugCheckAddress.ToInt64():X}");
- SafeMemory.ReadBytes(DebugCheckAddress, this.nop.Length, out this.original);
- SafeMemory.WriteBytes(DebugCheckAddress, this.nop);
- } else {
- Log.Information("DebugCheck already overwritten?");
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SigScanner instance.
+ public AntiDebug(SigScanner scanner)
+ {
+ try
+ {
+ this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41");
+ }
+ catch (KeyNotFoundException)
+ {
+ this.debugCheckAddress = IntPtr.Zero;
}
- IsEnabled = true;
+ Log.Verbose("DebugCheck address {DebugCheckAddress}", this.debugCheckAddress);
}
- public void Dispose() {
- //if (this.DebugCheckAddress != IntPtr.Zero && this.original != null)
- // Marshal.Copy(this.original, 0, DebugCheckAddress, this.nop.Length);
+ ///
+ /// Gets a value indicating whether the anti-debugging is enabled.
+ ///
+ public bool IsEnabled { get; private set; }
+
+ ///
+ /// 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 @ 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 @ 0x{this.debugCheckAddress.ToInt64():X}");
+ SafeMemory.WriteBytes(this.debugCheckAddress, this.original);
+ }
+ else
+ {
+ Log.Information("Debug check was not overwritten?");
+ }
+
+ this.IsEnabled = false;
+ }
+ }
+
+ ///
+ /// Implementing IDisposable.
+ ///
+ public 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.
+ // this.Disable();
+ }
}
}
}
diff --git a/Dalamud/Game/Internal/BaseAddressResolver.cs b/Dalamud/Game/Internal/BaseAddressResolver.cs
index 6adac49f9..287bacaaa 100644
--- a/Dalamud/Game/Internal/BaseAddressResolver.cs
+++ b/Dalamud/Game/Internal/BaseAddressResolver.cs
@@ -3,57 +3,103 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
-namespace Dalamud.Game.Internal {
- public abstract class BaseAddressResolver {
+namespace Dalamud.Game.Internal
+{
+ ///
+ /// Base memory address resolver.
+ ///
+ public abstract class BaseAddressResolver
+ {
+ ///
+ /// A list of memory addresses that were found, to list in /xldata.
+ ///
+ public static Dictionary> DebugScannedValues = new();
+
+ ///
+ /// Gets or sets a value indicating whether the resolver has successfully run or .
+ ///
protected bool IsResolved { get; set; }
- public static Dictionary> DebugScannedValues = new Dictionary>();
-
- public void Setup(SigScanner scanner) {
+ ///
+ /// Setup the resolver, calling the appopriate method based on the process architecture.
+ ///
+ /// The SigScanner instance.
+ public void Setup(SigScanner scanner)
+ {
// Because C# don't allow to call virtual function while in ctor
// we have to do this shit :\
-
- if (IsResolved) {
+
+ if (this.IsResolved)
+ {
return;
}
-
- if (scanner.Is32BitProcess) {
- Setup32Bit(scanner);
- } else {
- Setup64Bit(scanner);
- }
- SetupInternal(scanner);
- var className = GetType().Name;
+ if (scanner.Is32BitProcess)
+ {
+ this.Setup32Bit(scanner);
+ }
+ else
+ {
+ this.Setup64Bit(scanner);
+ }
+
+ this.SetupInternal(scanner);
+
+ var className = this.GetType().Name;
DebugScannedValues[className] = new List<(string, IntPtr)>();
- foreach (var property in GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) {
- DebugScannedValues[className].Add((property.Name, (IntPtr) property.GetValue(this)));
+ foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
+ {
+ DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this)));
}
- IsResolved = true;
+ this.IsResolved = true;
}
-
- protected virtual void Setup32Bit(SigScanner scanner) {
+
+ ///
+ /// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
+ ///
+ /// The delegate to marshal the function pointer to.
+ /// The address of the virtual table.
+ /// The offset from address to the vtable pointer.
+ /// The vfunc index.
+ /// A delegate function pointer that can be invoked.
+ public T GetVirtualFunction(IntPtr address, int vtableOffset, int count) where T : class
+ {
+ // Get vtable
+ var vtable = Marshal.ReadIntPtr(address, vtableOffset);
+
+ // Get an address to the function
+ var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
+
+ return Marshal.GetDelegateForFunctionPointer(functionAddress);
+ }
+
+ ///
+ /// Setup the resolver by finding any necessary memory addresses.
+ ///
+ /// The SigScanner instance.
+ protected virtual void Setup32Bit(SigScanner scanner)
+ {
throw new NotSupportedException("32 bit version is not supported.");
}
- protected virtual void Setup64Bit(SigScanner sig) {
+ ///
+ /// Setup the resolver by finding any necessary memory addresses.
+ ///
+ /// The SigScanner instance.
+ protected virtual void Setup64Bit(SigScanner scanner)
+ {
throw new NotSupportedException("64 bit version is not supported.");
}
- protected virtual void SetupInternal(SigScanner scanner) {
- // Do nothing
- }
-
- public T GetVirtualFunction(IntPtr address, int vtableOffset, int count) where T : class {
- // Get vtable
- var vtable = Marshal.ReadIntPtr(address, vtableOffset);
-
- // Get an address to the function
- var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
-
- return Marshal.GetDelegateForFunctionPointer(functionAddress);
+ ///
+ /// Setup the resolver by finding any necessary memory addresses.
+ ///
+ /// The SigScanner instance.
+ protected virtual void SetupInternal(SigScanner scanner)
+ {
+ // Do nothing
}
}
}
diff --git a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs b/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs
index d1fbe2bb8..eb867dd5c 100644
--- a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs
+++ b/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs
@@ -1,8 +1,20 @@
using System;
-namespace Dalamud.Game.Internal.DXGI {
- public interface ISwapChainAddressResolver {
+namespace Dalamud.Game.Internal.DXGI
+{
+ ///
+ /// An interface binding for the address resolvers that attempt to find native D3D11 methods.
+ ///
+ public interface ISwapChainAddressResolver
+ {
+ ///
+ /// Gets or sets the address of the native D3D11.Present method.
+ ///
IntPtr Present { get; set; }
+
+ ///
+ /// Gets or sets the address of the native D3D11.ResizeBuffers method.
+ ///
IntPtr ResizeBuffers { get; set; }
}
}
diff --git a/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs
index 4b0b1f8e8..701068ed3 100644
--- a/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs
+++ b/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs
@@ -1,18 +1,23 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+
using Serilog;
namespace Dalamud.Game.Internal.DXGI
{
+ ///
+ /// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI.
+ ///
public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver
{
+ ///
public IntPtr Present { get; set; }
+
+ ///
public IntPtr ResizeBuffers { get; set; }
+ ///
protected override void Setup64Bit(SigScanner sig)
{
var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "dxgi.dll");
@@ -22,9 +27,9 @@ namespace Dalamud.Game.Internal.DXGI
var scanner = new SigScanner(module);
// This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function.
- Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37;
+ this.Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37;
- ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24");
+ this.ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24");
}
}
}
diff --git a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs
index 29729f7a8..6e69a17bd 100644
--- a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs
+++ b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs
@@ -1,25 +1,69 @@
-using SharpDX.Direct3D;
-using SharpDX.Direct3D11;
-using SharpDX.DXGI;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
-using SharpDX.Windows;
+using System.Windows.Forms;
+
+using SharpDX.Direct3D;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+
using Device = SharpDX.Direct3D11.Device;
namespace Dalamud.Game.Internal.DXGI
{
- /*
- * This method of getting the SwapChain Addresses is currently not used.
- * If the normal AddressResolver(SigScanner) fails, we should use it as a fallback.(Linux?)
- */
+ ///
+ /// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it.
+ ///
+ ///
+ /// If the normal signature based method of resolution fails, this is the backup.
+ ///
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
{
private const int DxgiSwapchainMethodCount = 18;
private const int D3D11DeviceMethodCount = 43;
- private static SwapChainDescription CreateSwapChainDescription(IntPtr renderForm) {
- return new SwapChainDescription {
+ private List d3d11VTblAddresses;
+ private List dxgiSwapChainVTblAddresses;
+
+ ///
+ public IntPtr Present { get; set; }
+
+ ///
+ public IntPtr ResizeBuffers { get; set; }
+
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ if (this.d3d11VTblAddresses == null)
+ {
+ // Create temporary device + swapchain and determine method addresses
+ var renderForm = new Form();
+
+ Device.CreateWithSwapChain(
+ DriverType.Hardware,
+ DeviceCreationFlags.BgraSupport,
+ CreateSwapChainDescription(renderForm.Handle),
+ out var device,
+ out var swapChain);
+
+ if (device != null && swapChain != null)
+ {
+ this.d3d11VTblAddresses = this.GetVTblAddresses(device.NativePointer, D3D11DeviceMethodCount);
+ this.dxgiSwapChainVTblAddresses = this.GetVTblAddresses(swapChain.NativePointer, DxgiSwapchainMethodCount);
+ }
+
+ device?.Dispose();
+ swapChain?.Dispose();
+ }
+
+ this.Present = this.dxgiSwapChainVTblAddresses[8];
+ this.ResizeBuffers = this.dxgiSwapChainVTblAddresses[13];
+ }
+
+ private static SwapChainDescription CreateSwapChainDescription(IntPtr renderForm)
+ {
+ return new SwapChainDescription
+ {
BufferCount = 1,
Flags = SwapChainFlags.None,
IsWindowed = true,
@@ -27,74 +71,23 @@ namespace Dalamud.Game.Internal.DXGI
OutputHandle = renderForm,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
- Usage = Usage.RenderTargetOutput
+ Usage = Usage.RenderTargetOutput,
};
}
- private IntPtr[] GetVTblAddresses(IntPtr pointer, int numberOfMethods)
+ private List GetVTblAddresses(IntPtr pointer, int numberOfMethods)
{
- return GetVTblAddresses(pointer, 0, numberOfMethods);
+ return this.GetVTblAddresses(pointer, 0, numberOfMethods);
}
- private IntPtr[] GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods)
+ private List GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods)
{
- List vtblAddresses = new List();
- IntPtr vTable = Marshal.ReadIntPtr(pointer);
- for (int i = startIndex; i < startIndex + numberOfMethods; i++)
+ var vtblAddresses = new List();
+ var vTable = Marshal.ReadIntPtr(pointer);
+ for (var i = startIndex; i < startIndex + numberOfMethods; i++)
vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes
- return vtblAddresses.ToArray();
- }
-
- private List d3d11VTblAddresses = null;
- private List dxgiSwapChainVTblAddresses = null;
-
- #region Internal device resources
-
- private Device device;
- private SwapChain swapChain;
- private RenderForm renderForm;
- #endregion
-
- #region Addresses
-
- public IntPtr Present { get; set; }
- public IntPtr ResizeBuffers { get; set; }
-
- #endregion
-
- protected override void Setup64Bit(SigScanner sig) {
- if (this.d3d11VTblAddresses == null) {
- this.d3d11VTblAddresses = new List();
- this.dxgiSwapChainVTblAddresses = new List();
-
- #region Get Device and SwapChain method addresses
-
- // Create temporary device + swapchain and determine method addresses
- this.renderForm = new RenderForm();
- Device.CreateWithSwapChain(
- DriverType.Hardware,
- DeviceCreationFlags.BgraSupport,
- CreateSwapChainDescription(this.renderForm.Handle),
- out this.device,
- out this.swapChain
- );
-
- if (this.device != null && this.swapChain != null) {
- this.d3d11VTblAddresses.AddRange(
- GetVTblAddresses(this.device.NativePointer, D3D11DeviceMethodCount));
- this.dxgiSwapChainVTblAddresses.AddRange(
- GetVTblAddresses(this.swapChain.NativePointer, DxgiSwapchainMethodCount));
- }
-
- this.device?.Dispose();
- this.swapChain?.Dispose();
-
- #endregion
- }
-
- Present = this.dxgiSwapChainVTblAddresses[8];
- ResizeBuffers = this.dxgiSwapChainVTblAddresses[13];
+ return vtblAddresses;
}
}
}
diff --git a/Dalamud/Game/Internal/Framework.cs b/Dalamud/Game/Internal/Framework.cs
index 33fc80353..7e0d3ab8a 100644
--- a/Dalamud/Game/Internal/Framework.cs
+++ b/Dalamud/Game/Internal/Framework.cs
@@ -3,95 +3,154 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
-using System.Threading;
+
using Dalamud.Game.Internal.Gui;
using Dalamud.Game.Internal.Libc;
using Dalamud.Game.Internal.Network;
using Dalamud.Hooking;
using Serilog;
-namespace Dalamud.Game.Internal {
+namespace Dalamud.Game.Internal
+{
///
/// This class represents the Framework of the native game client and grants access to various subsystems.
///
- public sealed class Framework : IDisposable {
- private readonly Dalamud dalamud;
+ public sealed class Framework : IDisposable
+ {
+ private static Stopwatch statsStopwatch = new();
- internal bool DispatchUpdateEvents { get; set; } = true;
+ private readonly Dalamud dalamud;
+ private Hook updateHook;
+ private Hook destroyHook;
+ private Hook realDestroyHook;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SigScanner instance.
+ /// The Dalamud instance.
+ public Framework(SigScanner scanner, Dalamud dalamud)
+ {
+ this.dalamud = dalamud;
+ this.Address = new FrameworkAddressResolver();
+ this.Address.Setup(scanner);
+
+ Log.Verbose("Framework address {FrameworkAddress}", this.Address.BaseAddress);
+ if (this.Address.BaseAddress == IntPtr.Zero)
+ {
+ throw new InvalidOperationException("Framework is not initalized yet.");
+ }
+
+ // Hook virtual functions
+ this.HookVTable();
+
+ // Initialize subsystems
+ this.Libc = new LibcFunction(scanner);
+
+ this.Gui = new GameGui(this.Address.GuiManager, scanner, dalamud);
+
+ this.Network = new GameNetwork(scanner);
+ }
+
+ ///
+ /// A delegate type used with the event.
+ ///
+ /// The Framework instance.
+ public delegate void OnUpdateDelegate(Framework framework);
+
+ ///
+ /// A delegate type used during the native Framework::destroy.
+ ///
+ /// The native Framework address.
+ /// A value indicating if the call was successful.
+ public delegate bool OnRealDestroyDelegate(IntPtr framework);
+
+ ///
+ /// A delegate type used during the native Framework::free.
+ ///
+ /// The native Framework address.
+ public delegate IntPtr OnDestroyDelegate();
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate bool OnUpdateDetour(IntPtr framework);
- private delegate IntPtr OnDestroyDetour();
-
- public delegate void OnUpdateDelegate(Framework framework);
-
- public delegate IntPtr OnDestroyDelegate();
-
- public delegate bool OnRealDestroyDelegate(IntPtr framework);
+ private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
///
/// Event that gets fired every time the game framework updates.
///
public event OnUpdateDelegate OnUpdateEvent;
-
- private Hook updateHook;
- private Hook destroyHook;
-
- private Hook realDestroyHook;
-
///
- /// A raw pointer to the instance of Client::Framework
+ /// Gets or sets a value indicating whether the collection of stats is enabled.
///
- public FrameworkAddressResolver Address { get; }
-
- #region Stats
public static bool StatsEnabled { get; set; }
- public static Dictionary> StatsHistory = new Dictionary>();
- private static Stopwatch statsStopwatch = new Stopwatch();
-#endregion
-#region Subsystems
///
- /// The GUI subsystem, used to access e.g. chat.
+ /// Gets the stats history mapping.
+ ///
+ public static Dictionary> StatsHistory = new();
+
+ #region Subsystems
+
+ ///
+ /// Gets the GUI subsystem, used to access e.g. chat.
///
public GameGui Gui { get; private set; }
///
- /// The Network subsystem, used to access network data.
+ /// Gets the Network subsystem, used to access network data.
///
public GameNetwork Network { get; private set; }
- //public ResourceManager Resource { get; private set; }
-
+ // public ResourceManager Resource { get; private set; }
+
+ ///
+ /// Gets the Libc subsystem, used to facilitate interop with std::strings.
+ ///
public LibcFunction Libc { get; private set; }
#endregion
-
- public Framework(SigScanner scanner, Dalamud dalamud) {
- this.dalamud = dalamud;
- Address = new FrameworkAddressResolver();
- Address.Setup(scanner);
-
- Log.Verbose("Framework address {FrameworkAddress}", Address.BaseAddress);
- if (Address.BaseAddress == IntPtr.Zero) {
- throw new InvalidOperationException("Framework is not initalized yet.");
- }
-
- // Hook virtual functions
- HookVTable();
-
- // Initialize subsystems
- Libc = new LibcFunction(scanner);
-
- Gui = new GameGui(Address.GuiManager, scanner, dalamud);
- Network = new GameNetwork(scanner);
+ ///
+ /// Gets a raw pointer to the instance of Client::Framework.
+ ///
+ public FrameworkAddressResolver Address { get; }
+
+ ///
+ /// Gets or sets a value indicating whether to dispatch update events.
+ ///
+ internal bool DispatchUpdateEvents { get; set; } = true;
+
+ ///
+ /// Enable this module.
+ ///
+ public void Enable()
+ {
+ this.Gui.Enable();
+ this.Network.Enable();
+
+ this.updateHook.Enable();
+ this.destroyHook.Enable();
+ this.realDestroyHook.Enable();
}
- private void HookVTable() {
- var vtable = Marshal.ReadIntPtr(Address.BaseAddress);
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ this.Gui.Dispose();
+ this.Network.Dispose();
+
+ this.updateHook.Dispose();
+ this.destroyHook.Dispose();
+ this.realDestroyHook.Dispose();
+ }
+
+ private void HookVTable()
+ {
+ var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress);
// Virtual function layout:
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
@@ -100,36 +159,17 @@ namespace Dalamud.Game.Internal {
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
- this.updateHook = new Hook(pUpdate, new OnUpdateDetour(HandleFrameworkUpdate), this);
+ this.updateHook = new Hook(pUpdate, new OnUpdateDetour(this.HandleFrameworkUpdate), this);
var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3);
- this.destroyHook =
- new Hook(pDestroy, new OnDestroyDelegate(HandleFrameworkDestroy), this);
+ this.destroyHook = new Hook(pDestroy, new OnDestroyDelegate(this.HandleFrameworkDestroy), this);
var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2);
- this.realDestroyHook =
- new Hook(pRealDestroy, new OnRealDestroyDelegate(HandleRealDestroy), this);
- }
-
- public void Enable() {
- Gui.Enable();
- Network.Enable();
-
- this.updateHook.Enable();
- this.destroyHook.Enable();
- this.realDestroyHook.Enable();
- }
-
- public void Dispose() {
- Gui.Dispose();
- Network.Dispose();
-
- this.updateHook.Dispose();
- this.destroyHook.Dispose();
- this.realDestroyHook.Dispose();
+ this.realDestroyHook = new Hook(pRealDestroy, new OnRealDestroyDelegate(this.HandleRealDestroy), this);
}
- private bool HandleFrameworkUpdate(IntPtr framework) {
+ private bool HandleFrameworkUpdate(IntPtr framework)
+ {
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
if (!this.dalamud.IsReady)
this.dalamud.LoadTier2();
@@ -137,47 +177,62 @@ namespace Dalamud.Game.Internal {
if (!this.dalamud.IsLoadedPluginSystem && this.dalamud.InterfaceManager.IsReady)
this.dalamud.LoadTier3();
- try {
- Gui.Chat.UpdateQueue(this);
- Gui.Toast.UpdateQueue();
- Network.UpdateQueue(this);
- } catch (Exception ex) {
+ try
+ {
+ this.Gui.Chat.UpdateQueue(this);
+ this.Gui.Toast.UpdateQueue();
+ this.Network.UpdateQueue(this);
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Exception while handling Framework::Update hook.");
}
if (this.DispatchUpdateEvents)
{
- try {
- if (StatsEnabled && OnUpdateEvent != null) {
+ try
+ {
+ if (StatsEnabled && this.OnUpdateEvent != null)
+ {
// Stat Tracking for Framework Updates
- var invokeList = OnUpdateEvent.GetInvocationList();
+ var invokeList = this.OnUpdateEvent.GetInvocationList();
var notUpdated = StatsHistory.Keys.ToList();
// Individually invoke OnUpdate handlers and time them.
- foreach (var d in invokeList) {
+ foreach (var d in invokeList)
+ {
statsStopwatch.Restart();
- d.Method.Invoke(d.Target, new object[]{ this });
+ 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());
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
- if (StatsHistory[key].Count > 1000) {
+ 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) {
+ foreach (var key in notUpdated)
+ {
+ if (StatsHistory[key].Count > 0)
+ {
StatsHistory[key].RemoveAt(0);
- } else {
+ }
+ else
+ {
StatsHistory.Remove(key);
}
}
- } else {
- OnUpdateEvent?.Invoke(this);
}
- } catch (Exception ex) {
+ else
+ {
+ this.OnUpdateEvent?.Invoke(this);
+ }
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Exception while dispatching Framework::Update event.");
}
}
@@ -199,7 +254,8 @@ namespace Dalamud.Game.Internal {
return this.realDestroyHook.Original(framework);
}
- private IntPtr HandleFrameworkDestroy() {
+ private IntPtr HandleFrameworkDestroy()
+ {
Log.Information("Framework::Free!");
// Store the pointer to the original trampoline location
diff --git a/Dalamud/Game/Internal/FrameworkAddressResolver.cs b/Dalamud/Game/Internal/FrameworkAddressResolver.cs
index 14ea52ba8..551e779e5 100644
--- a/Dalamud/Game/Internal/FrameworkAddressResolver.cs
+++ b/Dalamud/Game/Internal/FrameworkAddressResolver.cs
@@ -1,26 +1,43 @@
using System;
using System.Runtime.InteropServices;
-namespace Dalamud.Game.Internal {
- public sealed class FrameworkAddressResolver : BaseAddressResolver {
+namespace Dalamud.Game.Internal
+{
+ ///
+ /// The address resolver for the class.
+ ///
+ public sealed class FrameworkAddressResolver : BaseAddressResolver
+ {
+ ///
+ /// Gets the base address native Framework class.
+ ///
public IntPtr BaseAddress { get; private set; }
-
+
+ ///
+ /// Gets the address for the native GuiManager class.
+ ///
public IntPtr GuiManager { get; private set; }
-
+
+ ///
+ /// Gets the address for the native ScriptManager class.
+ ///
public IntPtr ScriptManager { get; private set; }
- protected override void Setup64Bit(SigScanner sig) {
- SetupFramework(sig);
-
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ this.SetupFramework(sig);
+
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
// Xiv__Framework__GetGuiManager+F 000 retn
- GuiManager = Marshal.ReadIntPtr(BaseAddress, 0x2C08);
+ this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08);
// Called from Framework::Init
- ScriptManager = BaseAddress + 0x2C68; // note that no deref here
+ this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here
}
- private void SetupFramework(SigScanner scanner) {
+ private void SetupFramework(SigScanner scanner)
+ {
// Dissasembly of part of the .dtor
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
@@ -30,9 +47,9 @@ namespace Dalamud.Game.Internal {
var fwDtor = scanner.ScanText("48C705????????00000000 E8???????? 488D??????0000 E8???????? 488D");
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
-
+
// Framework does not change once initialized in startup so don't bother to deref again and again.
- BaseAddress = Marshal.ReadIntPtr(pFramework);
+ this.BaseAddress = Marshal.ReadIntPtr(pFramework);
}
}
}
diff --git a/Dalamud/Game/Internal/Gui/Addon/Addon.cs b/Dalamud/Game/Internal/Gui/Addon/Addon.cs
index 0c82f5df6..5a0c4ce98 100644
--- a/Dalamud/Game/Internal/Gui/Addon/Addon.cs
+++ b/Dalamud/Game/Internal/Gui/Addon/Addon.cs
@@ -1,27 +1,66 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-using System.Threading.Tasks;
-namespace Dalamud.Game.Internal.Gui.Addon {
- public class Addon {
+namespace Dalamud.Game.Internal.Gui.Addon
+{
+ ///
+ /// This class represents an in-game UI "Addon".
+ ///
+ public class Addon
+ {
+ ///
+ /// The address of the addon.
+ ///
public IntPtr Address;
+
+ ///
+ /// The addon interop data.
+ ///
protected Structs.Addon addonStruct;
-
- public Addon(IntPtr address, Structs.Addon addonStruct) {
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The address of the addon.
+ /// The addon interop data.
+ public Addon(IntPtr address, Structs.Addon addonStruct)
+ {
this.Address = address;
this.addonStruct = addonStruct;
}
+ ///
+ /// Gets the name of the addon.
+ ///
public string Name => this.addonStruct.Name;
- public short X => this.addonStruct.X;
- public short Y => this.addonStruct.Y;
- public float Scale => this.addonStruct.Scale;
- public unsafe float Width => this.addonStruct.RootNode->Width * Scale;
- public unsafe float Height => this.addonStruct.RootNode->Height * Scale;
+ ///
+ /// Gets the X position of the addon on screen.
+ ///
+ public short X => this.addonStruct.X;
+
+ ///
+ /// Gets the Y position of the addon on screen.
+ ///
+ public short Y => this.addonStruct.Y;
+
+ ///
+ /// Gets the scale of the addon.
+ ///
+ public float Scale => this.addonStruct.Scale;
+
+ ///
+ /// Gets the width of the addon. This may include non-visible parts.
+ ///
+ public unsafe float Width => this.addonStruct.RootNode->Width * this.Scale;
+
+ ///
+ /// Gets the height of the addon. This may include non-visible parts.
+ ///
+ public unsafe float Height => this.addonStruct.RootNode->Height * this.Scale;
+
+ ///
+ /// Gets a value indicating whether the addon is visible.
+ ///
public bool Visible => (this.addonStruct.Flags & 0x20) == 0x20;
}
}
diff --git a/Dalamud/Game/Internal/Gui/ChatGui.cs b/Dalamud/Game/Internal/Gui/ChatGui.cs
index 4a542a79b..111cc259c 100644
--- a/Dalamud/Game/Internal/Gui/ChatGui.cs
+++ b/Dalamud/Game/Internal/Gui/ChatGui.cs
@@ -3,35 +3,127 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
+
+using Dalamud.Game.Internal.Libc;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
-using Dalamud.Game.Internal.Libc;
using Dalamud.Hooking;
using Serilog;
-namespace Dalamud.Game.Internal.Gui {
- public sealed class ChatGui : IDisposable {
- private readonly Queue chatQueue = new Queue();
+namespace Dalamud.Game.Internal.Gui
+{
+ ///
+ /// This class handles interacting with the native chat UI.
+ ///
+ public sealed class ChatGui : IDisposable
+ {
+ private readonly Dalamud dalamud;
+ private readonly ChatGuiAddressResolver address;
- #region Events
+ private readonly Queue chatQueue = new();
+ private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new();
- public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
- public delegate void OnMessageRawDelegate(XivChatType type, uint senderId, ref StdString sender, ref StdString message, ref bool isHandled);
- public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
- public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
- public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
+ private readonly Hook printMessageHook;
+ private readonly Hook populateItemLinkHook;
+ private readonly Hook interactableLinkClickedHook;
+
+ private IntPtr baseAddress = IntPtr.Zero;
///
- /// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
+ /// Initializes a new instance of the class.
///
- public event OnCheckMessageHandledDelegate OnCheckMessageHandled;
+ /// The base address of the ChatManager.
+ /// The SigScanner instance.
+ /// The Dalamud instance.
+ public ChatGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud)
+ {
+ this.dalamud = dalamud;
+
+ this.address = new ChatGuiAddressResolver(baseAddress);
+ this.address.Setup(scanner);
+
+ Log.Verbose("Chat manager address {ChatManager}", this.address.BaseAddress);
+
+ this.printMessageHook = new Hook(this.address.PrintMessage, new PrintMessageDelegate(this.HandlePrintMessageDetour), this);
+ this.populateItemLinkHook = new Hook(this.address.PopulateItemLinkObject, new PopulateItemLinkDelegate(this.HandlePopulateItemLinkDetour), this);
+ this.interactableLinkClickedHook = new Hook(this.address.InteractableLinkClicked, new InteractableLinkClickedDelegate(this.InteractableLinkClickedDetour));
+ }
+
+ ///
+ /// A delegate type used with the event.
+ ///
+ /// The type of chat.
+ /// The sender ID.
+ /// The sender name.
+ /// The message sent.
+ /// A value indicating whether the message was handled or should be propagated.
+ public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
+
+ ///
+ /// A delegate type used with the event.
+ ///
+ /// The type of chat.
+ /// The sender ID.
+ /// The sender name.
+ /// The message sent.
+ /// A value indicating whether the message was handled or should be propagated.
+ [Obsolete("Please use OnMessageDelegate instead. For modifications, it will take precedence.")]
+ public delegate void OnMessageRawDelegate(XivChatType type, uint senderId, ref StdString sender, ref StdString message, ref bool isHandled);
+
+ ///
+ /// A delegate type used with the event.
+ ///
+ /// The type of chat.
+ /// The sender ID.
+ /// The sender name.
+ /// The message sent.
+ /// A value indicating whether the message was handled or should be propagated.
+ public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
+
+ ///
+ /// A delegate type used with the event.
+ ///
+ /// The type of chat.
+ /// The sender ID.
+ /// The sender name.
+ /// The message sent.
+ public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
+
+ ///
+ /// A delegate type used with the event.
+ ///
+ /// The type of chat.
+ /// The sender ID.
+ /// The sender name.
+ /// The message sent.
+ public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
///
/// Event that will be fired when a chat message is sent to chat by the game.
///
public event OnMessageDelegate OnChatMessage;
+ ///
+ /// Event that will be fired when a chat message is sent by the game, containing raw, unparsed data.
+ ///
+ [Obsolete("Please use OnChatMessage instead. For modifications, it will take precedence.")]
+ public event OnMessageRawDelegate OnChatMessageRaw;
+
+ ///
+ /// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
+ ///
+ public event OnCheckMessageHandledDelegate OnCheckMessageHandled;
+
///
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
///
@@ -43,101 +135,239 @@ namespace Dalamud.Game.Internal.Gui {
public event OnMessageUnhandledDelegate OnChatMessageUnhandled;
///
- /// Event that will be fired when a chat message is sent by the game, containing raw, unparsed data.
+ /// Gets the ID of the last linked item.
///
- [Obsolete("Please use OnChatMessage instead. For modifications, it will take precedence.")]
- public event OnMessageRawDelegate OnChatMessageRaw;
-
- #endregion
-
- #region Hooks
-
- private readonly Hook printMessageHook;
-
- private readonly Hook populateItemLinkHook;
-
- private readonly Hook interactableLinkClickedHook;
-
- #endregion
-
- #region Delegates
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName,
- IntPtr message,
- uint senderId, IntPtr parameter);
-
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
-
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
-
- #endregion
-
public int LastLinkedItemId { get; private set; }
+
+ ///
+ /// Gets the flags of the last linked item.
+ ///
public byte LastLinkedItemFlags { get; private set; }
- private ChatGuiAddressResolver Address { get; }
-
- private IntPtr baseAddress = IntPtr.Zero;
-
- private readonly Dalamud dalamud;
-
- public ChatGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud) {
- this.dalamud = dalamud;
-
- Address = new ChatGuiAddressResolver(baseAddress);
- Address.Setup(scanner);
-
- Log.Verbose("Chat manager address {ChatManager}", Address.BaseAddress);
-
- this.printMessageHook =
- new Hook(Address.PrintMessage, new PrintMessageDelegate(HandlePrintMessageDetour),
- this);
- this.populateItemLinkHook =
- new Hook(Address.PopulateItemLinkObject,
- new PopulateItemLinkDelegate(HandlePopulateItemLinkDetour),
- this);
- this.interactableLinkClickedHook =
- new Hook(Address.InteractableLinkClicked,
- new InteractableLinkClickedDelegate(InteractableLinkClickedDetour));
-
- }
-
- public void Enable() {
+ ///
+ /// Enables this module.
+ ///
+ public void Enable()
+ {
this.printMessageHook.Enable();
this.populateItemLinkHook.Enable();
this.interactableLinkClickedHook.Enable();
}
- public void Dispose() {
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ public void Dispose()
+ {
this.printMessageHook.Dispose();
this.populateItemLinkHook.Dispose();
this.interactableLinkClickedHook.Dispose();
}
- private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) {
- try {
+ ///
+ /// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
+ /// later to be processed when UpdateQueue() is called.
+ ///
+ /// A message to send.
+ public void PrintChat(XivChatEntry chat)
+ {
+ this.chatQueue.Enqueue(chat);
+ }
+
+ ///
+ /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
+ /// later to be processed when UpdateQueue() is called.
+ ///
+ /// A message to send.
+ public void Print(string message)
+ {
+ Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
+ this.PrintChat(new XivChatEntry
+ {
+ MessageBytes = Encoding.UTF8.GetBytes(message),
+ Type = this.dalamud.Configuration.GeneralChatType,
+ });
+ }
+
+ ///
+ /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
+ /// later to be processed when UpdateQueue() is called.
+ ///
+ /// A message to send.
+ public void Print(SeString message)
+ {
+ Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
+ this.PrintChat(new XivChatEntry
+ {
+ MessageBytes = message.Encode(),
+ Type = this.dalamud.Configuration.GeneralChatType,
+ });
+ }
+
+ ///
+ /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
+ /// the queue, later to be processed when UpdateQueue() is called.
+ ///
+ /// A message to send.
+ public void PrintError(string message)
+ {
+ Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
+ this.PrintChat(new XivChatEntry
+ {
+ MessageBytes = Encoding.UTF8.GetBytes(message),
+ Type = XivChatType.Urgent,
+ });
+ }
+
+ ///
+ /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
+ /// the queue, later to be processed when UpdateQueue() is called.
+ ///
+ /// A message to send.
+ public void PrintError(SeString message)
+ {
+ Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
+ this.PrintChat(new XivChatEntry
+ {
+ MessageBytes = message.Encode(),
+ Type = XivChatType.Urgent,
+ });
+ }
+
+ ///
+ /// Process a chat queue.
+ ///
+ /// The Framework instance.
+ public void UpdateQueue(Framework framework)
+ {
+ while (this.chatQueue.Count > 0)
+ {
+ var chat = this.chatQueue.Dequeue();
+
+ if (this.baseAddress == IntPtr.Zero)
+ {
+ continue;
+ }
+
+ var senderRaw = Encoding.UTF8.GetBytes(chat.Name ?? string.Empty);
+ using var senderOwned = framework.Libc.NewString(senderRaw);
+
+ var messageRaw = chat.MessageBytes ?? new byte[0];
+ using var messageOwned = framework.Libc.NewString(messageRaw);
+
+ this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
+ }
+ }
+
+ ///
+ /// Create a link handler.
+ ///
+ /// The name of the plugin handling the link.
+ /// The ID of the command to run.
+ /// The command action itself.
+ /// A payload for handling.
+ internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction)
+ {
+ var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
+ this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
+ return payload;
+ }
+
+ ///
+ /// Remove all handlers owned by a plugin.
+ ///
+ /// The name of the plugin handling the links.
+ internal void RemoveChatLinkHandler(string pluginName)
+ {
+ foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName))
+ {
+ this.dalamudLinkHandlers.Remove(handler);
+ }
+ }
+
+ ///
+ /// Remove a registered link handler.
+ ///
+ /// The name of the plugin handling the link.
+ /// The ID of the command to be removed.
+ internal void RemoveChatLinkHandler(string pluginName, uint commandId)
+ {
+ if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
+ {
+ this.dalamudLinkHandlers.Remove((pluginName, commandId));
+ }
+ }
+
+ private static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2)
+ {
+ // Copyright (c) 2008-2013 Hafthor Stefansson
+ // Distributed under the MIT/X11 software license
+ // Ref: http://www.opensource.org/licenses/mit-license.php.
+ // https://stackoverflow.com/a/8808245
+
+ if (a1 == a2) return true;
+ if (a1 == null || a2 == null || a1.Length != a2.Length)
+ return false;
+ fixed (byte* p1 = a1, p2 = a2)
+ {
+ byte* x1 = p1, x2 = p2;
+ var l = a1.Length;
+ for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
+ {
+ if (*((long*)x1) != *((long*)x2))
+ return false;
+ }
+
+ if ((l & 4) != 0)
+ {
+ if (*((int*)x1) != *((int*)x2))
+ return false;
+ x1 += 4;
+ x2 += 4;
+ }
+
+ if ((l & 2) != 0)
+ {
+ if (*((short*)x1) != *((short*)x2))
+ return false;
+ x1 += 2;
+ x2 += 2;
+ }
+
+ if ((l & 1) != 0)
+ {
+ if (*((byte*)x1) != *((byte*)x2))
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
+ {
+ try
+ {
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
- LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
- LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
+ this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
+ this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
- Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{LastLinkedItemId}");
- } catch (Exception ex) {
+ Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Exception onPopulateItemLink hook.");
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
}
}
- private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage,
- uint senderid, IntPtr parameter) {
+ private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
+ {
var retVal = IntPtr.Zero;
- try {
+ try
+ {
var sender = StdString.ReadFromPointer(pSenderName);
var message = StdString.ReadFromPointer(pMessage);
@@ -146,32 +376,35 @@ namespace Dalamud.Game.Internal.Gui {
Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
- //Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
+ // Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
- var originalMessageData = (byte[]) message.RawData.Clone();
+ var originalMessageData = (byte[])message.RawData.Clone();
var oldEdited = parsedMessage.Encode();
// Call events
var isHandled = false;
- OnCheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
+ this.OnCheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
- if (!isHandled) {
- OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
- OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled);
+ if (!isHandled)
+ {
+ this.OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
+ this.OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled);
}
var newEdited = parsedMessage.Encode();
- if (!FastByteArrayCompare(oldEdited, newEdited)) {
+ if (!FastByteArrayCompare(oldEdited, newEdited))
+ {
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
message.RawData = newEdited;
Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
- }
+ }
var messagePtr = pMessage;
OwnedStdString allocatedString = null;
- if (!FastByteArrayCompare(originalMessageData, message.RawData)) {
+ if (!FastByteArrayCompare(originalMessageData, message.RawData))
+ {
allocatedString = this.dalamud.Framework.Libc.NewString(message.RawData);
Log.Debug(
$"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
@@ -181,19 +414,21 @@ namespace Dalamud.Game.Internal.Gui {
// Print the original chat if it's handled.
if (isHandled)
{
- OnChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
+ this.OnChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
}
else
{
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter);
- OnChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
+ this.OnChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
}
if (this.baseAddress == IntPtr.Zero)
this.baseAddress = manager;
allocatedString?.Dispose();
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Exception on OnChatMessage hook.");
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
}
@@ -201,47 +436,14 @@ namespace Dalamud.Game.Internal.Gui {
return retVal;
}
- private readonly Dictionary<(string pluginName, uint commandId), Action> dalamudLinkHandlers = new Dictionary<(string, uint), Action>();
-
- ///
- /// Create a link handler
- ///
- ///
- ///
- ///
- ///
- internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction) {
- var payload = new DalamudLinkPayload() {Plugin = pluginName, CommandId = commandId};
- this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
- return payload;
- }
-
- ///
- /// Remove a registered link handler
- ///
- ///
- ///
- internal void RemoveChatLinkHandler(string pluginName, uint commandId) {
- if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId))) {
- this.dalamudLinkHandlers.Remove((pluginName, commandId));
- }
- }
-
- ///
- /// Remove all handlers owned by a plugin.
- ///
- ///
- internal void RemoveChatLinkHandler(string pluginName) {
- foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.pluginName == pluginName)) {
- this.dalamudLinkHandlers.Remove(handler);
- }
- }
-
- private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) {
- try {
+ private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
+ {
+ try
+ {
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
- if (interactableType != Payload.EmbeddedInfoType.DalamudLink) {
+ if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
+ {
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
return;
}
@@ -258,100 +460,22 @@ namespace Dalamud.Game.Internal.Gui {
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
if (payloads.Count == 0) return;
var linkPayload = payloads[0];
- if (linkPayload is DalamudLinkPayload link) {
- if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) {
+ if (linkPayload is DalamudLinkPayload link)
+ {
+ if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
+ {
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
- } else {
+ }
+ else
+ {
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
}
}
- } catch (Exception ex) {
- Log.Error(ex, "Exception on InteractableLinkClicked hook");
}
- }
-
- // Copyright (c) 2008-2013 Hafthor Stefansson
- // Distributed under the MIT/X11 software license
- // Ref: http://www.opensource.org/licenses/mit-license.php.
- // https://stackoverflow.com/a/8808245
- static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2)
- {
- if (a1 == a2) return true;
- if (a1 == null || a2 == null || a1.Length != a2.Length)
- return false;
- fixed (byte* p1 = a1, p2 = a2)
+ catch (Exception ex)
{
- byte* x1 = p1, x2 = p2;
- int l = a1.Length;
- for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
- if (*((long*)x1) != *((long*)x2)) return false;
- if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; }
- if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; }
- if ((l & 1) != 0) if (*((byte*)x1) != *((byte*)x2)) return false;
- return true;
- }
- }
-
- ///
- /// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
- /// later to be processed when UpdateQueue() is called.
- ///
- /// A message to send.
- public void PrintChat(XivChatEntry chat) {
- this.chatQueue.Enqueue(chat);
- }
-
- public void Print(string message) {
- Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
- PrintChat(new XivChatEntry {
- MessageBytes = Encoding.UTF8.GetBytes(message),
- Type = this.dalamud.Configuration.GeneralChatType
- });
- }
-
- public void Print(SeString message) {
- Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
- PrintChat(new XivChatEntry {
- MessageBytes = message.Encode(),
- Type = this.dalamud.Configuration.GeneralChatType
- });
- }
-
- public void PrintError(string message) {
- Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
- PrintChat(new XivChatEntry {
- MessageBytes = Encoding.UTF8.GetBytes(message),
- Type = XivChatType.Urgent
- });
- }
-
- public void PrintError(SeString message) {
- Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
- PrintChat(new XivChatEntry {
- MessageBytes = message.Encode(),
- Type = XivChatType.Urgent
- });
- }
-
- ///
- /// Process a chat queue.
- ///
- public void UpdateQueue(Framework framework) {
- while (this.chatQueue.Count > 0) {
- var chat = this.chatQueue.Dequeue();
-
- if (this.baseAddress == IntPtr.Zero) {
- continue;
- }
-
- var senderRaw = Encoding.UTF8.GetBytes(chat.Name ?? "");
- using var senderOwned = framework.Libc.NewString(senderRaw);
-
- var messageRaw = chat.MessageBytes ?? new byte[0];
- using var messageOwned = framework.Libc.NewString(messageRaw);
-
- this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
+ Log.Error(ex, "Exception on InteractableLinkClicked hook");
}
}
}
diff --git a/Dalamud/Game/Internal/Gui/ChatGuiAddressResolver.cs b/Dalamud/Game/Internal/Gui/ChatGuiAddressResolver.cs
index 0620f93b5..dfc6ed0f3 100644
--- a/Dalamud/Game/Internal/Gui/ChatGuiAddressResolver.cs
+++ b/Dalamud/Game/Internal/Gui/ChatGuiAddressResolver.cs
@@ -1,97 +1,118 @@
using System;
-namespace Dalamud.Game.Internal.Gui {
- public sealed class ChatGuiAddressResolver : BaseAddressResolver {
- public IntPtr BaseAddress { get; }
-
- public IntPtr PrintMessage { get; private set; }
- public IntPtr PopulateItemLinkObject { get; private set; }
- public IntPtr InteractableLinkClicked { get; private set; }
-
- public ChatGuiAddressResolver(IntPtr baseAddres) {
- BaseAddress = baseAddres;
+namespace Dalamud.Game.Internal.Gui
+{
+ ///
+ /// The address resolver for the class.
+ ///
+ public sealed class ChatGuiAddressResolver : BaseAddressResolver
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The base address of the native ChatManager class.
+ public ChatGuiAddressResolver(IntPtr baseAddres)
+ {
+ this.BaseAddress = baseAddres;
}
-
-
+
+ ///
+ /// Gets the base address of the native ChatManager class.
+ ///
+ public IntPtr BaseAddress { get; }
+
+ ///
+ /// Gets the address of the native PrintMessage method.
+ ///
+ public IntPtr PrintMessage { get; private set; }
+
+ ///
+ /// Gets the address of the native PopulateItemLinkObject method.
+ ///
+ public IntPtr PopulateItemLinkObject { get; private set; }
+
+ ///
+ /// Gets the address of the native InteractableLinkClicked method.
+ ///
+ public IntPtr InteractableLinkClicked { get; private set; }
+
/*
--- for reference: 4.57 ---
-
-.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
-.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
-.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
-.text:00000001405CD210 ; sub_140141D10+220↑p ...
-.text:00000001405CD210
-.text:00000001405CD210 var_220 = qword ptr -220h
-.text:00000001405CD210 var_218 = byte ptr -218h
-.text:00000001405CD210 var_210 = word ptr -210h
-.text:00000001405CD210 var_208 = byte ptr -208h
-.text:00000001405CD210 var_200 = word ptr -200h
-.text:00000001405CD210 var_1FC = dword ptr -1FCh
-.text:00000001405CD210 var_1F8 = qword ptr -1F8h
-.text:00000001405CD210 var_1F0 = qword ptr -1F0h
-.text:00000001405CD210 var_1E8 = qword ptr -1E8h
-.text:00000001405CD210 var_1E0 = dword ptr -1E0h
-.text:00000001405CD210 var_1DC = word ptr -1DCh
-.text:00000001405CD210 var_1DA = word ptr -1DAh
-.text:00000001405CD210 var_1D8 = qword ptr -1D8h
-.text:00000001405CD210 var_1D0 = byte ptr -1D0h
-.text:00000001405CD210 var_1C8 = qword ptr -1C8h
-.text:00000001405CD210 var_1B0 = dword ptr -1B0h
-.text:00000001405CD210 var_1AC = dword ptr -1ACh
-.text:00000001405CD210 var_1A8 = dword ptr -1A8h
-.text:00000001405CD210 var_1A4 = dword ptr -1A4h
-.text:00000001405CD210 var_1A0 = dword ptr -1A0h
-.text:00000001405CD210 var_160 = dword ptr -160h
-.text:00000001405CD210 var_15C = dword ptr -15Ch
-.text:00000001405CD210 var_140 = dword ptr -140h
-.text:00000001405CD210 var_138 = dword ptr -138h
-.text:00000001405CD210 var_130 = byte ptr -130h
-.text:00000001405CD210 var_C0 = byte ptr -0C0h
-.text:00000001405CD210 var_50 = qword ptr -50h
-.text:00000001405CD210 var_38 = qword ptr -38h
-.text:00000001405CD210 var_30 = qword ptr -30h
-.text:00000001405CD210 var_28 = qword ptr -28h
-.text:00000001405CD210 var_20 = qword ptr -20h
-.text:00000001405CD210 senderActorId = dword ptr 30h
-.text:00000001405CD210 isLocal = byte ptr 38h
-.text:00000001405CD210
-.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
-.text:00000001405CD210 push rbp
-.text:00000001405CD212 push rdi
-.text:00000001405CD213 push r14
-.text:00000001405CD215 push r15
-.text:00000001405CD217 lea rbp, [rsp-128h]
-.text:00000001405CD21F sub rsp, 228h
-.text:00000001405CD226 mov rax, cs:__security_cookie
-.text:00000001405CD22D xor rax, rsp
-.text:00000001405CD230 mov [rbp+140h+var_50], rax
-.text:00000001405CD237 xor r10b, r10b
-.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
-.text:00000001405CD23F xor eax, eax
-.text:00000001405CD241 mov r11, r9
-.text:00000001405CD244 mov r14, r8
-.text:00000001405CD247 mov r9d, eax
-.text:00000001405CD24A movzx r15d, dx
-.text:00000001405CD24E lea r8, [rcx+0C10h]
-.text:00000001405CD255 mov rdi, rcx
+ .text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
+ .text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
+ .text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
+ .text:00000001405CD210 ; sub_140141D10+220↑p ...
+ .text:00000001405CD210
+ .text:00000001405CD210 var_220 = qword ptr -220h
+ .text:00000001405CD210 var_218 = byte ptr -218h
+ .text:00000001405CD210 var_210 = word ptr -210h
+ .text:00000001405CD210 var_208 = byte ptr -208h
+ .text:00000001405CD210 var_200 = word ptr -200h
+ .text:00000001405CD210 var_1FC = dword ptr -1FCh
+ .text:00000001405CD210 var_1F8 = qword ptr -1F8h
+ .text:00000001405CD210 var_1F0 = qword ptr -1F0h
+ .text:00000001405CD210 var_1E8 = qword ptr -1E8h
+ .text:00000001405CD210 var_1E0 = dword ptr -1E0h
+ .text:00000001405CD210 var_1DC = word ptr -1DCh
+ .text:00000001405CD210 var_1DA = word ptr -1DAh
+ .text:00000001405CD210 var_1D8 = qword ptr -1D8h
+ .text:00000001405CD210 var_1D0 = byte ptr -1D0h
+ .text:00000001405CD210 var_1C8 = qword ptr -1C8h
+ .text:00000001405CD210 var_1B0 = dword ptr -1B0h
+ .text:00000001405CD210 var_1AC = dword ptr -1ACh
+ .text:00000001405CD210 var_1A8 = dword ptr -1A8h
+ .text:00000001405CD210 var_1A4 = dword ptr -1A4h
+ .text:00000001405CD210 var_1A0 = dword ptr -1A0h
+ .text:00000001405CD210 var_160 = dword ptr -160h
+ .text:00000001405CD210 var_15C = dword ptr -15Ch
+ .text:00000001405CD210 var_140 = dword ptr -140h
+ .text:00000001405CD210 var_138 = dword ptr -138h
+ .text:00000001405CD210 var_130 = byte ptr -130h
+ .text:00000001405CD210 var_C0 = byte ptr -0C0h
+ .text:00000001405CD210 var_50 = qword ptr -50h
+ .text:00000001405CD210 var_38 = qword ptr -38h
+ .text:00000001405CD210 var_30 = qword ptr -30h
+ .text:00000001405CD210 var_28 = qword ptr -28h
+ .text:00000001405CD210 var_20 = qword ptr -20h
+ .text:00000001405CD210 senderActorId = dword ptr 30h
+ .text:00000001405CD210 isLocal = byte ptr 38h
+ .text:00000001405CD210
+ .text:00000001405CD210 ; __unwind { // __GSHandlerCheck
+ .text:00000001405CD210 push rbp
+ .text:00000001405CD212 push rdi
+ .text:00000001405CD213 push r14
+ .text:00000001405CD215 push r15
+ .text:00000001405CD217 lea rbp, [rsp-128h]
+ .text:00000001405CD21F sub rsp, 228h
+ .text:00000001405CD226 mov rax, cs:__security_cookie
+ .text:00000001405CD22D xor rax, rsp
+ .text:00000001405CD230 mov [rbp+140h+var_50], rax
+ .text:00000001405CD237 xor r10b, r10b
+ .text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
+ .text:00000001405CD23F xor eax, eax
+ .text:00000001405CD241 mov r11, r9
+ .text:00000001405CD244 mov r14, r8
+ .text:00000001405CD247 mov r9d, eax
+ .text:00000001405CD24A movzx r15d, dx
+ .text:00000001405CD24E lea r8, [rcx+0C10h]
+ .text:00000001405CD255 mov rdi, rcx
*/
-
- protected override void Setup64Bit(SigScanner sig) {
- //PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
- PrintMessage =
- sig.ScanText(
- "4055 53 56 4154 4157 48 8d ac 24 ?? ?? ?? ?? 48 81 ec 20 02 00 00 48 8b 05"
- );
- //PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
- //PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
+ this.PrintMessage = sig.ScanText("4055 53 56 4154 4157 48 8d ac 24 ?? ?? ?? ?? 48 81 ec 20 02 00 00 48 8b 05");
+ // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
- //PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
+ // PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
- //PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
- PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
+ // PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
- InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
+ // PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
+ this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
+
+ this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
}
}
}
diff --git a/Dalamud/Game/Internal/Gui/GameGui.cs b/Dalamud/Game/Internal/Gui/GameGui.cs
index 318e2a576..b8bcf012b 100644
--- a/Dalamud/Game/Internal/Gui/GameGui.cs
+++ b/Dalamud/Game/Internal/Gui/GameGui.cs
@@ -1,292 +1,218 @@
using System;
using System.Runtime.InteropServices;
+
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Hooking;
using Dalamud.Interface;
-using ImGuiNET;
using Serilog;
using SharpDX;
-namespace Dalamud.Game.Internal.Gui {
+namespace Dalamud.Game.Internal.Gui
+{
+ ///
+ /// A class handling many aspects of the in-game UI.
+ ///
public sealed class GameGui : IDisposable
{
- private readonly Dalamud dalamud;
-
- private GameGuiAddressResolver Address { get; }
-
- public ChatGui Chat { get; private set; }
- public PartyFinderGui PartyFinder { get; private set; }
- public ToastGui Toast { get; private set; }
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr SetGlobalBgmDelegate(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6);
- private readonly Hook setGlobalBgmHook;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
- private readonly Hook handleItemHoverHook;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
- private readonly Hook handleItemOutHook;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5);
- private readonly Hook handleActionHoverHook;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
- private Hook handleActionOutHook;
-
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- private delegate IntPtr GetUIObjectDelegate();
- private readonly GetUIObjectDelegate getUIObject;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr GetUIMapObjectDelegate(IntPtr UIObject);
- private GetUIMapObjectDelegate getUIMapObject;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
- private delegate bool OpenMapWithFlagDelegate(IntPtr UIMapObject, string flag);
- private OpenMapWithFlagDelegate openMapWithFlag;
-
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- internal delegate IntPtr GetMatrixSingletonDelegate();
- internal readonly GetMatrixSingletonDelegate getMatrixSingleton;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private unsafe delegate bool ScreenToWorldNativeDelegate(
- float *camPos, float *clipPos, float rayDistance, float *worldPos, int *unknown);
- private readonly ScreenToWorldNativeDelegate screenToWorldNative;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
- private readonly Hook toggleUiHideHook;
-
- // Return a Client::UI::UIModule
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate IntPtr GetBaseUIObjectDelegate();
+ ///
+ /// The delegate of the native method that gets the Client::UI::UIModule address.
+ ///
+ /// The Client::UI::UIModule address.
public readonly GetBaseUIObjectDelegate GetBaseUIObject;
- [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
- private delegate IntPtr GetUIObjectByNameDelegate(IntPtr thisPtr, string uiName, int index);
+ private readonly Dalamud dalamud;
+ private readonly GameGuiAddressResolver address;
+
+ private readonly GetMatrixSingletonDelegate getMatrixSingleton;
+ private readonly GetUIObjectDelegate getUIObject;
+ private readonly ScreenToWorldNativeDelegate screenToWorldNative;
private readonly GetUIObjectByNameDelegate getUIObjectByName;
-
- private delegate IntPtr GetUiModuleDelegate(IntPtr basePtr);
private readonly GetUiModuleDelegate getUiModule;
+ private readonly GetAgentModuleDelegate getAgentModule;
- private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule);
- private GetAgentModuleDelegate getAgentModule;
+ private readonly Hook setGlobalBgmHook;
+ private readonly Hook handleItemHoverHook;
+ private readonly Hook handleItemOutHook;
+ private readonly Hook handleActionHoverHook;
+ private readonly Hook handleActionOutHook;
+ private readonly Hook toggleUiHideHook;
- public bool GameUiHidden { get; private set; }
+ private GetUIMapObjectDelegate getUIMapObject;
+ private OpenMapWithFlagDelegate openMapWithFlag;
///
- /// Event which is fired when the game UI hiding is toggled.
+ /// Initializes a new instance of the class.
+ /// This class is responsible for many aspects of interacting with the native game UI.
///
- public event EventHandler OnUiHideToggled;
-
- ///
- /// The item ID that is currently hovered by the player. 0 when no item is hovered.
- /// If > 1.000.000, subtract 1.000.000 and treat it as HQ
- ///
- public ulong HoveredItem { get; set; }
-
- ///
- /// The action ID that is current hovered by the player. 0 when no action is hovered.
- ///
- public HoveredAction HoveredAction { get; } = new HoveredAction();
-
- ///
- /// Event that is fired when the currently hovered item changes.
- ///
- public EventHandler HoveredItemChanged { get; set; }
-
- ///
- /// Event that is fired when the currently hovered action changes.
- ///
- public EventHandler HoveredActionChanged { get; set; }
-
+ /// The base address of the native GuiManager class.
+ /// The SigScanner instance.
+ /// The Dalamud instance.
public GameGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud)
{
this.dalamud = dalamud;
- Address = new GameGuiAddressResolver(baseAddress);
- Address.Setup(scanner);
+ this.address = new GameGuiAddressResolver(baseAddress);
+ this.address.Setup(scanner);
Log.Verbose("===== G A M E G U I =====");
- Log.Verbose("GameGuiManager address {Address}", Address.BaseAddress);
- Log.Verbose("SetGlobalBgm address {Address}", Address.SetGlobalBgm);
- Log.Verbose("HandleItemHover address {Address}", Address.HandleItemHover);
- Log.Verbose("HandleItemOut address {Address}", Address.HandleItemOut);
- Log.Verbose("GetUIObject address {Address}", Address.GetUIObject);
- Log.Verbose("GetAgentModule address {Address}", Address.GetAgentModule);
+ Log.Verbose("GameGuiManager address {Address:X}", this.address.BaseAddress.ToInt64());
+ Log.Verbose("SetGlobalBgm address {Address:X}", this.address.SetGlobalBgm.ToInt64());
+ Log.Verbose("HandleItemHover address {Address:X}", this.address.HandleItemHover.ToInt64());
+ Log.Verbose("HandleItemOut address {Address:X}", this.address.HandleItemOut.ToInt64());
+ Log.Verbose("GetUIObject address {Address:X}", this.address.GetUIObject.ToInt64());
+ Log.Verbose("GetAgentModule address {Address:X}", this.address.GetAgentModule.ToInt64());
- Chat = new ChatGui(Address.ChatManager, scanner, dalamud);
- PartyFinder = new PartyFinderGui(scanner, dalamud);
- Toast = new ToastGui(scanner, dalamud);
+ this.Chat = new ChatGui(this.address.ChatManager, scanner, dalamud);
+ this.PartyFinder = new PartyFinderGui(scanner, dalamud);
+ this.Toast = new ToastGui(scanner, dalamud);
- this.setGlobalBgmHook =
- new Hook(Address.SetGlobalBgm,
- new SetGlobalBgmDelegate(HandleSetGlobalBgmDetour),
- this);
- this.handleItemHoverHook =
- new Hook(Address.HandleItemHover,
- new HandleItemHoverDelegate(HandleItemHoverDetour),
- this);
+ this.setGlobalBgmHook = new Hook(this.address.SetGlobalBgm, new SetGlobalBgmDelegate(this.HandleSetGlobalBgmDetour), this);
+ this.handleItemHoverHook = new Hook(this.address.HandleItemHover, new HandleItemHoverDelegate(this.HandleItemHoverDetour), this);
- this.handleItemOutHook =
- new Hook(Address.HandleItemOut,
- new HandleItemOutDelegate(HandleItemOutDetour),
- this);
+ this.handleItemOutHook = new Hook(this.address.HandleItemOut, new HandleItemOutDelegate(this.HandleItemOutDetour), this);
- this.handleActionHoverHook =
- new Hook(Address.HandleActionHover,
- new HandleActionHoverDelegate(HandleActionHoverDetour),
- this);
- this.handleActionOutHook =
- new Hook(Address.HandleActionOut,
- new HandleActionOutDelegate(HandleActionOutDetour),
- this);
-
- this.getUIObject = Marshal.GetDelegateForFunctionPointer(Address.GetUIObject);
+ this.handleActionHoverHook = new Hook(this.address.HandleActionHover, new HandleActionHoverDelegate(this.HandleActionHoverDetour), this);
+ this.handleActionOutHook = new Hook(this.address.HandleActionOut, new HandleActionOutDelegate(this.HandleActionOutDetour), this);
- this.getMatrixSingleton =
- Marshal.GetDelegateForFunctionPointer(Address.GetMatrixSingleton);
+ this.getUIObject = Marshal.GetDelegateForFunctionPointer(this.address.GetUIObject);
- this.screenToWorldNative =
- Marshal.GetDelegateForFunctionPointer(Address.ScreenToWorld);
+ this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton);
- this.toggleUiHideHook = new Hook(Address.ToggleUiHide, new ToggleUiHideDelegate(ToggleUiHideDetour), this);
+ this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld);
- this.GetBaseUIObject = Marshal.GetDelegateForFunctionPointer(Address.GetBaseUIObject);
- this.getUIObjectByName = Marshal.GetDelegateForFunctionPointer(Address.GetUIObjectByName);
+ this.toggleUiHideHook = new Hook(this.address.ToggleUiHide, new ToggleUiHideDelegate(this.ToggleUiHideDetour), this);
- this.getUiModule = Marshal.GetDelegateForFunctionPointer(Address.GetUIModule);
- this.getAgentModule = Marshal.GetDelegateForFunctionPointer(Address.GetAgentModule);
+ this.GetBaseUIObject = Marshal.GetDelegateForFunctionPointer(this.address.GetBaseUIObject);
+ this.getUIObjectByName = Marshal.GetDelegateForFunctionPointer(this.address.GetUIObjectByName);
+
+ this.getUiModule = Marshal.GetDelegateForFunctionPointer(this.address.GetUIModule);
+ this.getAgentModule = Marshal.GetDelegateForFunctionPointer(this.address.GetAgentModule);
}
- private IntPtr HandleSetGlobalBgmDetour(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6) {
- var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
-
- Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal);
-
- return retVal;
- }
-
- private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) {
- var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
-
- if (retVal.ToInt64() == 22) {
- var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
- this.HoveredItem = itemId;
-
- try {
- HoveredItemChanged?.Invoke(this, itemId);
- } catch (Exception e) {
- Log.Error(e, "Could not dispatch HoveredItemChanged event.");
- }
-
- Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X"));
- }
-
- return retVal;
- }
-
- private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
- {
- var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
-
- if (a3 != IntPtr.Zero && a4 == 1) {
- var a3Val = Marshal.ReadByte(a3, 0x8);
-
- if (a3Val == 255) {
- this.HoveredItem = 0ul;
-
- try {
- HoveredItemChanged?.Invoke(this, 0ul);
- } catch (Exception e) {
- Log.Error(e, "Could not dispatch HoveredItemChanged event.");
- }
-
- Log.Verbose("HoverItemId: 0");
- }
- }
-
- return retVal;
- }
-
- private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
- {
- handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
- HoveredAction.ActionKind = actionKind;
- HoveredAction.BaseActionID = actionId;
- HoveredAction.ActionID = (uint) Marshal.ReadInt32(hoverState, 0x3C);
- try
- {
- HoveredActionChanged?.Invoke(this, this.HoveredAction);
- } catch (Exception e)
- {
- Log.Error(e, "Could not dispatch HoveredItemChanged event.");
- }
- Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X"));
- }
-
- private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
- {
- var retVal = handleActionOutHook.Original(agentActionDetail, a2, a3, a4);
-
- if (a3 != IntPtr.Zero && a4 == 1)
- {
- var a3Val = Marshal.ReadByte(a3, 0x8);
-
- if (a3Val == 255)
- {
- this.HoveredAction.ActionKind = HoverActionKind.None;
- HoveredAction.BaseActionID = 0;
- HoveredAction.ActionID = 0;
-
- try
- {
- HoveredActionChanged?.Invoke(this, this.HoveredAction);
- } catch (Exception e)
- {
- Log.Error(e, "Could not dispatch HoveredActionChanged event.");
- }
-
- Log.Verbose("HoverActionId: 0");
- }
- }
-
- return retVal;
- }
+ // Marshaled delegates
///
- /// Opens the in-game map with a flag on the location of the parameter
+ /// The delegate type of the native method that gets the Client::UI::UIModule address.
///
- /// Link to the map to be opened
- /// True if there were no errors and it could open the map
- public bool OpenMapWithMapLink(MapLinkPayload mapLink) {
+ /// The Client::UI::UIModule address.
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr GetBaseUIObjectDelegate();
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ private delegate IntPtr GetMatrixSingletonDelegate();
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ private delegate IntPtr GetUIObjectDelegate();
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private unsafe delegate bool ScreenToWorldNativeDelegate(float* camPos, float* clipPos, float rayDistance, float* worldPos, int* unknown);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
+ private delegate IntPtr GetUIObjectByNameDelegate(IntPtr thisPtr, string uiName, int index);
+
+ private delegate IntPtr GetUiModuleDelegate(IntPtr basePtr);
+
+ private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule);
+
+ // Hooked delegates
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
+ private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
+
+ ///
+ /// Event which is fired when the game UI hiding is toggled.
+ ///
+ public event EventHandler OnUiHideToggled;
+
+ ///
+ /// Gets the instance.
+ ///
+ public ChatGui Chat { get; private set; }
+
+ ///
+ /// Gets the instance.
+ ///
+ public PartyFinderGui PartyFinder { get; private set; }
+
+ ///
+ /// Gets the instance.
+ ///
+ public ToastGui Toast { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the game UI is hidden.
+ ///
+ public bool GameUiHidden { get; private set; }
+
+ ///
+ /// Gets or sets the item ID that is currently hovered by the player. 0 when no item is hovered.
+ /// If > 1.000.000, subtract 1.000.000 and treat it as HQ.
+ ///
+ public ulong HoveredItem { get; set; }
+
+ ///
+ /// Gets the action ID that is current hovered by the player. 0 when no action is hovered.
+ ///
+ public HoveredAction HoveredAction { get; } = new HoveredAction();
+
+ ///
+ /// Gets or sets the event that is fired when the currently hovered item changes.
+ ///
+ public EventHandler HoveredItemChanged { get; set; }
+
+ ///
+ /// Gets or sets the event that is fired when the currently hovered action changes.
+ ///
+ public EventHandler HoveredActionChanged { get; set; }
+
+ ///
+ /// Opens the in-game map with a flag on the location of the parameter.
+ ///
+ /// Link to the map to be opened.
+ /// True if there were no errors and it could open the map.
+ public bool OpenMapWithMapLink(MapLinkPayload mapLink)
+ {
var uiObjectPtr = this.getUIObject();
- if (uiObjectPtr.Equals(IntPtr.Zero)) {
+ if (uiObjectPtr.Equals(IntPtr.Zero))
+ {
Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()");
return false;
}
- this.getUIMapObject =
- Address.GetVirtualFunction(uiObjectPtr, 0, 8);
-
+ this.getUIMapObject = this.address.GetVirtualFunction(uiObjectPtr, 0, 8);
var uiMapObjectPtr = this.getUIMapObject(uiObjectPtr);
- if (uiMapObjectPtr.Equals(IntPtr.Zero)) {
+ if (uiMapObjectPtr.Equals(IntPtr.Zero))
+ {
Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()");
return false;
}
- this.openMapWithFlag =
- Address.GetVirtualFunction(uiMapObjectPtr, 0, 63);
+ this.openMapWithFlag = this.address.GetVirtualFunction(uiMapObjectPtr, 0, 63);
var mapLinkString = mapLink.DataString;
@@ -298,35 +224,36 @@ namespace Dalamud.Game.Internal.Gui {
///
/// Converts in-world coordinates to screen coordinates (upper left corner origin).
///
- /// Coordinates in the world
- /// Converted coordinates
- /// True if worldPos corresponds to a position in front of the camera
+ /// Coordinates in the world.
+ /// Converted coordinates.
+ /// True if worldPos corresponds to a position in front of the camera.
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
{
// Get base object with matrices
var matrixSingleton = this.getMatrixSingleton();
// Read current ViewProjectionMatrix plus game window size
- var viewProjectionMatrix = new Matrix();
+ var viewProjectionMatrix = default(Matrix);
float width, height;
var windowPos = ImGuiHelpers.MainViewport.Pos;
- unsafe {
- var rawMatrix = (float*) (matrixSingleton + 0x1b4).ToPointer();
+ unsafe
+ {
+ var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer();
for (var i = 0; i < 16; i++, rawMatrix++)
viewProjectionMatrix[i] = *rawMatrix;
- width = *rawMatrix;
+ width = *rawMatrix;
height = *(rawMatrix + 1);
}
- Vector3.Transform( ref worldPos, ref viewProjectionMatrix, out Vector3 pCoords);
+ Vector3.Transform(ref worldPos, ref viewProjectionMatrix, out Vector3 pCoords);
screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z);
- screenPos.X = 0.5f * width * (screenPos.X + 1f) + windowPos.X;
- screenPos.Y = 0.5f * height * (1f - screenPos.Y) + windowPos.Y;
+ screenPos.X = (0.5f * width * (screenPos.X + 1f)) + windowPos.X;
+ screenPos.Y = (0.5f * height * (1f - screenPos.Y)) + windowPos.Y;
return pCoords.Z > 0 &&
screenPos.X > windowPos.X && screenPos.X < windowPos.X + width &&
@@ -336,10 +263,10 @@ namespace Dalamud.Game.Internal.Gui {
///
/// Converts screen coordinates to in-world coordinates via raycasting.
///
- /// Screen coordinates
- /// Converted coordinates
- /// How far to search for a collision
- /// True if successful. On false, worldPos's contents are undefined
+ /// Screen coordinates.
+ /// Converted coordinates.
+ /// How far to search for a collision.
+ /// True if successful. On false, worldPos's contents are undefined.
public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f)
{
// The game is only visible in the main viewport, so if the cursor is outside
@@ -350,7 +277,7 @@ namespace Dalamud.Game.Internal.Gui {
if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X ||
screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y)
{
- worldPos = new Vector3();
+ worldPos = default;
return false;
}
@@ -358,7 +285,7 @@ namespace Dalamud.Game.Internal.Gui {
var matrixSingleton = this.getMatrixSingleton();
// Read current ViewProjectionMatrix plus game window size
- var viewProjectionMatrix = new Matrix();
+ var viewProjectionMatrix = default(Matrix);
float width, height;
unsafe
{
@@ -374,14 +301,15 @@ namespace Dalamud.Game.Internal.Gui {
viewProjectionMatrix.Invert();
var localScreenPos = new Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y);
- var screenPos3D = new Vector3 {
- X = localScreenPos.X / width * 2.0f - 1.0f,
- Y = -(localScreenPos.Y / height * 2.0f - 1.0f),
- Z = 0
+ var screenPos3D = new Vector3
+ {
+ X = (localScreenPos.X / width * 2.0f) - 1.0f,
+ Y = -((localScreenPos.Y / height * 2.0f) - 1.0f),
+ Z = 0,
};
Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos);
-
+
screenPos3D.Z = 1;
Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPosOne);
@@ -389,7 +317,8 @@ namespace Dalamud.Game.Internal.Gui {
clipPos.Normalize();
bool isSuccess;
- unsafe {
+ unsafe
+ {
var camPosArray = camPos.ToArray();
var clipPosArray = clipPos.ToArray();
@@ -397,54 +326,46 @@ namespace Dalamud.Game.Internal.Gui {
var worldPosArray = stackalloc float[32];
// Theory: this is some kind of flag on what type of things the ray collides with
- var unknown = stackalloc int[3] { 0x4000, 0x4000, 0x0 };
+ var unknown = stackalloc int[3]
+ {
+ 0x4000,
+ 0x4000,
+ 0x0,
+ };
- fixed (float* pCamPos = camPosArray) {
- fixed (float* pClipPos = clipPosArray) {
+ fixed (float* pCamPos = camPosArray)
+ {
+ fixed (float* pClipPos = clipPosArray)
+ {
isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown);
}
}
- worldPos = new Vector3 {
+ worldPos = new Vector3
+ {
X = worldPosArray[0],
Y = worldPosArray[1],
- Z = worldPosArray[2]
+ Z = worldPosArray[2],
};
}
return isSuccess;
}
- private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) {
- GameUiHidden = !GameUiHidden;
-
- try {
- OnUiHideToggled?.Invoke(this, GameUiHidden);
- } catch (Exception ex) {
- Log.Error(ex, "Error on OnUiHideToggled event dispatch");
- }
-
- Log.Debug("UiHide toggled: {0}", GameUiHidden);
-
- return this.toggleUiHideHook.Original(thisPtr, unknownByte);
- }
-
///
/// Gets a pointer to the game's UI module.
///
- /// IntPtr pointing to UI module
- public IntPtr GetUIModule()
- {
- return this.getUiModule(this.dalamud.Framework.Address.BaseAddress);
- }
+ /// IntPtr pointing to UI module.
+ public IntPtr GetUIModule() => this.getUiModule(this.dalamud.Framework.Address.BaseAddress);
///
/// Gets the pointer to the UI Object with the given name and index.
///
- /// Name of UI to find
- /// Index of UI to find (1-indexed)
- /// IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the UI Object
- public IntPtr GetUiObjectByName(string name, int index) {
+ /// Name of UI to find.
+ /// Index of UI to find (1-indexed).
+ /// IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the UI Object.
+ public IntPtr GetUiObjectByName(string name, int index)
+ {
var baseUi = this.GetBaseUIObject();
if (baseUi == IntPtr.Zero) return IntPtr.Zero;
var baseUiProperties = Marshal.ReadIntPtr(baseUi, 0x20);
@@ -452,19 +373,36 @@ namespace Dalamud.Game.Internal.Gui {
return this.getUIObjectByName(baseUiProperties, name, index);
}
- public Addon.Addon GetAddonByName(string name, int index) {
- var addonMem = GetUiObjectByName(name, index);
+ ///
+ /// Gets an Addon by it's internal name.
+ ///
+ /// The addon name.
+ /// The index of the addon, starting at 1.
+ /// The native memory representation of the addon, if it exists.
+ public Addon.Addon GetAddonByName(string name, int index)
+ {
+ var addonMem = this.GetUiObjectByName(name, index);
if (addonMem == IntPtr.Zero) return null;
var addonStruct = Marshal.PtrToStructure(addonMem);
return new Addon.Addon(addonMem, addonStruct);
}
+ ///
+ /// Find the agent associated with an addon, if possible.
+ ///
+ /// The addon name.
+ /// A pointer to the agent interface.
public IntPtr FindAgentInterface(string addonName)
{
var addon = this.dalamud.Framework.Gui.GetUiObjectByName(addonName, 1);
return this.FindAgentInterface(addon);
}
+ ///
+ /// Find the agent associated with an addon, if possible.
+ ///
+ /// The addon address.
+ /// A pointer to the agent interface.
public IntPtr FindAgentInterface(IntPtr addon)
{
if (addon == IntPtr.Zero)
@@ -501,12 +439,20 @@ namespace Dalamud.Game.Internal.Gui {
return IntPtr.Zero;
}
- public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
+ ///
+ /// Set the current background music.
+ ///
+ /// The background music key.
+ public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
- public void Enable() {
- Chat.Enable();
- Toast.Enable();
- PartyFinder.Enable();
+ ///
+ /// Enables the hooks and submodules of this module.
+ ///
+ public void Enable()
+ {
+ this.Chat.Enable();
+ this.Toast.Enable();
+ this.PartyFinder.Enable();
this.setGlobalBgmHook.Enable();
this.handleItemHoverHook.Enable();
this.handleItemOutHook.Enable();
@@ -515,10 +461,14 @@ namespace Dalamud.Game.Internal.Gui {
this.handleActionOutHook.Enable();
}
- public void Dispose() {
- Chat.Dispose();
- Toast.Dispose();
- PartyFinder.Dispose();
+ ///
+ /// Disables the hooks and submodules of this module.
+ ///
+ public void Dispose()
+ {
+ this.Chat.Dispose();
+ this.Toast.Dispose();
+ this.PartyFinder.Dispose();
this.setGlobalBgmHook.Dispose();
this.handleItemHoverHook.Dispose();
this.handleItemOutHook.Dispose();
@@ -526,5 +476,132 @@ namespace Dalamud.Game.Internal.Gui {
this.handleActionHoverHook.Dispose();
this.handleActionOutHook.Dispose();
}
+
+ private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6)
+ {
+ var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
+
+ Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal);
+
+ return retVal;
+ }
+
+ private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
+ {
+ var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
+
+ if (retVal.ToInt64() == 22)
+ {
+ var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
+ this.HoveredItem = itemId;
+
+ try
+ {
+ this.HoveredItemChanged?.Invoke(this, itemId);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Could not dispatch HoveredItemChanged event.");
+ }
+
+ Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X"));
+ }
+
+ return retVal;
+ }
+
+ private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
+ {
+ var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
+
+ if (a3 != IntPtr.Zero && a4 == 1)
+ {
+ var a3Val = Marshal.ReadByte(a3, 0x8);
+
+ if (a3Val == 255)
+ {
+ this.HoveredItem = 0ul;
+
+ try
+ {
+ this.HoveredItemChanged?.Invoke(this, 0ul);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Could not dispatch HoveredItemChanged event.");
+ }
+
+ Log.Verbose("HoverItemId: 0");
+ }
+ }
+
+ return retVal;
+ }
+
+ private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
+ {
+ this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
+ this.HoveredAction.ActionKind = actionKind;
+ this.HoveredAction.BaseActionID = actionId;
+ this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C);
+ try
+ {
+ this.HoveredActionChanged?.Invoke(this, this.HoveredAction);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Could not dispatch HoveredItemChanged event.");
+ }
+
+ Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X"));
+ }
+
+ private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
+ {
+ var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4);
+
+ if (a3 != IntPtr.Zero && a4 == 1)
+ {
+ var a3Val = Marshal.ReadByte(a3, 0x8);
+
+ if (a3Val == 255)
+ {
+ this.HoveredAction.ActionKind = HoverActionKind.None;
+ this.HoveredAction.BaseActionID = 0;
+ this.HoveredAction.ActionID = 0;
+
+ try
+ {
+ this.HoveredActionChanged?.Invoke(this, this.HoveredAction);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Could not dispatch HoveredActionChanged event.");
+ }
+
+ Log.Verbose("HoverActionId: 0");
+ }
+ }
+
+ return retVal;
+ }
+
+ private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte)
+ {
+ this.GameUiHidden = !this.GameUiHidden;
+
+ try
+ {
+ this.OnUiHideToggled?.Invoke(this, this.GameUiHidden);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Error on OnUiHideToggled event dispatch");
+ }
+
+ Log.Debug("UiHide toggled: {0}", this.GameUiHidden);
+
+ return this.toggleUiHideHook.Original(thisPtr, unknownByte);
+ }
}
}
diff --git a/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs
index b38acc7fd..12c46faa4 100644
--- a/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs
+++ b/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs
@@ -1,56 +1,123 @@
using System;
using System.Runtime.InteropServices;
-using Serilog;
-namespace Dalamud.Game.Internal.Gui {
- internal sealed class GameGuiAddressResolver : BaseAddressResolver {
+namespace Dalamud.Game.Internal.Gui
+{
+ ///
+ /// The address resolver for the class.
+ ///
+ internal sealed class GameGuiAddressResolver : BaseAddressResolver
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The base address of the native GuiManager class.
+ public GameGuiAddressResolver(IntPtr baseAddress)
+ {
+ this.BaseAddress = baseAddress;
+ }
+
+ ///
+ /// Gets the base address of the native GuiManager class.
+ ///
public IntPtr BaseAddress { get; private set; }
-
+
+ ///
+ /// Gets the address of the native ChatManager class.
+ ///
public IntPtr ChatManager { get; private set; }
+ ///
+ /// Gets the address of the native SetGlobalBgm method.
+ ///
public IntPtr SetGlobalBgm { get; private set; }
- public IntPtr HandleItemHover { get; set; }
- public IntPtr HandleItemOut { get; set; }
- public IntPtr HandleActionHover { get; set; }
- public IntPtr HandleActionOut { get; set; }
+
+ ///
+ /// Gets the address of the native HandleItemHover method.
+ ///
+ public IntPtr HandleItemHover { get; private set; }
+
+ ///
+ /// Gets the address of the native HandleItemOut method.
+ ///
+ public IntPtr HandleItemOut { get; private set; }
+
+ ///
+ /// Gets the address of the native HandleActionHover method.
+ ///
+ public IntPtr HandleActionHover { get; private set; }
+
+ ///
+ /// Gets the address of the native HandleActionOut method.
+ ///
+ public IntPtr HandleActionOut { get; private set; }
+
+ ///
+ /// Gets the address of the native GetUIObject method.
+ ///
public IntPtr GetUIObject { get; private set; }
+
+ ///
+ /// Gets the address of the native GetMatrixSingleton method.
+ ///
public IntPtr GetMatrixSingleton { get; private set; }
+
+ ///
+ /// Gets the address of the native ScreenToWorld method.
+ ///
public IntPtr ScreenToWorld { get; private set; }
- public IntPtr ToggleUiHide { get; set; }
+
+ ///
+ /// Gets the address of the native ToggleUiHide method.
+ ///
+ public IntPtr ToggleUiHide { get; private set; }
+
+ ///
+ /// Gets the address of the native Client::UI::UIModule getter method.
+ ///
public IntPtr GetBaseUIObject { get; private set; }
+
+ ///
+ /// Gets the address of the native GetUIObjectByName method.
+ ///
public IntPtr GetUIObjectByName { get; private set; }
+
+ ///
+ /// Gets the address of the native GetUIModule method.
+ ///
public IntPtr GetUIModule { get; private set; }
+
+ ///
+ /// Gets the address of the native GetAgentModule method.
+ ///
public IntPtr GetAgentModule { get; private set; }
- public GameGuiAddressResolver(IntPtr baseAddress) {
- BaseAddress = baseAddress;
- }
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr GetChatManagerDelegate(IntPtr guiManager);
-
- protected override void SetupInternal(SigScanner scanner) {
- // Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
- // Xiv__UiManager__GetChatManager+7 000 retn
- ChatManager = BaseAddress + 0x13E0;
- }
-
- protected override void Setup64Bit(SigScanner sig) {
- SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
- HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
- HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
- HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
- HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
- GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
- GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
- ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
- ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
- GetBaseUIObject = sig.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
- GetUIObjectByName = sig.ScanText("E8 ?? ?? ?? ?? 48 8B CF 48 89 87 ?? ?? 00 00 E8 ?? ?? ?? ?? 41 B8 01 00 00 00");
- GetUIModule = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 85 C0 75 2D");
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
+ this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
+ this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
+ this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
+ this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
+ this.GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
+ this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
+ this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
+ this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
+ this.GetBaseUIObject = sig.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
+ this.GetUIObjectByName = sig.ScanText("E8 ?? ?? ?? ?? 48 8B CF 48 89 87 ?? ?? 00 00 E8 ?? ?? ?? ?? 41 B8 01 00 00 00");
+ this.GetUIModule = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 85 C0 75 2D");
var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28");
this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size);
}
+
+ ///
+ protected override void SetupInternal(SigScanner scanner)
+ {
+ // Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
+ // Xiv__UiManager__GetChatManager+7 000 retn
+ this.ChatManager = this.BaseAddress + 0x13E0;
+ }
}
}
diff --git a/Dalamud/Game/Internal/Gui/HoverActionKind.cs b/Dalamud/Game/Internal/Gui/HoverActionKind.cs
index a3a8ad859..f069cb614 100644
--- a/Dalamud/Game/Internal/Gui/HoverActionKind.cs
+++ b/Dalamud/Game/Internal/Gui/HoverActionKind.cs
@@ -1,16 +1,49 @@
-namespace Dalamud.Game.Internal.Gui {
-
+namespace Dalamud.Game.Internal.Gui
+{
///
/// ActionKinds used in AgentActionDetail.
+ /// These describe the possible kinds of actions being hovered.
///
- public enum HoverActionKind {
+ public enum HoverActionKind
+ {
+ ///
+ /// No action is hovered.
+ ///
None = 0,
+
+ ///
+ /// A regular action is hovered.
+ ///
Action = 21,
+
+ ///
+ /// A general action is hovered.
+ ///
GeneralAction = 23,
+
+ ///
+ /// A companion order type of action is hovered.
+ ///
CompanionOrder = 24,
+
+ ///
+ /// A main command type of action is hovered.
+ ///
MainCommand = 25,
+
+ ///
+ /// An extras command type of action is hovered.
+ ///
ExtraCommand = 26,
+
+ ///
+ /// A pet order type of action is hovered.
+ ///
PetOrder = 28,
+
+ ///
+ /// A trait is hovered.
+ ///
Trait = 29,
}
}
diff --git a/Dalamud/Game/Internal/Gui/HoveredAction.cs b/Dalamud/Game/Internal/Gui/HoveredAction.cs
index 51d573bd7..ebc0dea15 100644
--- a/Dalamud/Game/Internal/Gui/HoveredAction.cs
+++ b/Dalamud/Game/Internal/Gui/HoveredAction.cs
@@ -1,18 +1,22 @@
-namespace Dalamud.Game.Internal.Gui {
- public class HoveredAction {
-
+namespace Dalamud.Game.Internal.Gui
+{
+ ///
+ /// This class represents the hotbar action currently hovered over by the cursor.
+ ///
+ public class HoveredAction
+ {
///
- /// The base action ID
+ /// Gets or sets the base action ID.
///
public uint BaseActionID { get; set; } = 0;
-
+
///
- /// Action ID accounting for automatic upgrades.
+ /// Gets or sets the action ID accounting for automatic upgrades.
///
public uint ActionID { get; set; } = 0;
-
+
///
- /// The type of action
+ /// Gets or sets the type of action.
///
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
}
diff --git a/Dalamud/Game/Internal/Gui/PartyFinderAddressResolver.cs b/Dalamud/Game/Internal/Gui/PartyFinderAddressResolver.cs
index 03e27b2a3..3c0f80e34 100755
--- a/Dalamud/Game/Internal/Gui/PartyFinderAddressResolver.cs
+++ b/Dalamud/Game/Internal/Gui/PartyFinderAddressResolver.cs
@@ -1,11 +1,21 @@
-using System;
+using System;
-namespace Dalamud.Game.Internal.Gui {
- class PartyFinderAddressResolver : BaseAddressResolver {
+namespace Dalamud.Game.Internal.Gui
+{
+ ///
+ /// The address resolver for the class.
+ ///
+ internal class PartyFinderAddressResolver : BaseAddressResolver
+ {
+ ///
+ /// Gets the address of the native ReceiveListing method.
+ ///
public IntPtr ReceiveListing { get; private set; }
- protected override void Setup64Bit(SigScanner sig) {
- ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
}
}
}
diff --git a/Dalamud/Game/Internal/Gui/PartyFinderGui.cs b/Dalamud/Game/Internal/Gui/PartyFinderGui.cs
index 87dd171c4..bdc5eee96 100755
--- a/Dalamud/Game/Internal/Gui/PartyFinderGui.cs
+++ b/Dalamud/Game/Internal/Gui/PartyFinderGui.cs
@@ -1,71 +1,90 @@
-using System;
+using System;
using System.Runtime.InteropServices;
+
using Dalamud.Game.Internal.Gui.Structs;
using Dalamud.Hooking;
using Serilog;
-namespace Dalamud.Game.Internal.Gui {
- public sealed class PartyFinderGui : IDisposable {
- #region Events
-
- public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
-
- ///
- /// Event fired each time the game receives an individual Party Finder listing. Cannot modify listings but can
- /// hide them.
- ///
- public event PartyFinderListingEventDelegate ReceiveListing;
-
- #endregion
-
- #region Hooks
+namespace Dalamud.Game.Internal.Gui
+{
+ ///
+ /// This class handles interacting with the native PartyFinder window.
+ ///
+ public sealed class PartyFinderGui : IDisposable
+ {
+ private readonly Dalamud dalamud;
+ private readonly PartyFinderAddressResolver address;
+ private readonly IntPtr memory;
private readonly Hook receiveListingHook;
- #endregion
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SigScanner instance.
+ /// The Dalamud instance.
+ public PartyFinderGui(SigScanner scanner, Dalamud dalamud)
+ {
+ this.dalamud = dalamud;
- #region Delegates
+ this.address = new PartyFinderAddressResolver();
+ this.address.Setup(scanner);
+
+ this.memory = Marshal.AllocHGlobal(PartyFinder.PacketInfo.PacketSize);
+
+ this.receiveListingHook = new Hook(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
+ }
+
+ ///
+ /// Event type fired each time the game receives an individual Party Finder listing.
+ /// Cannot modify listings but can hide them.
+ ///
+ /// The listings received.
+ /// Additional arguments passed by the game.
+ public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
- #endregion
+ ///
+ /// Event fired each time the game receives an individual Party Finder listing.
+ /// Cannot modify listings but can hide them.
+ ///
+ public event PartyFinderListingEventDelegate ReceiveListing;
- private Dalamud Dalamud { get; }
- private PartyFinderAddressResolver Address { get; }
- private IntPtr Memory { get; }
-
- public PartyFinderGui(SigScanner scanner, Dalamud dalamud) {
- Dalamud = dalamud;
-
- Address = new PartyFinderAddressResolver();
- Address.Setup(scanner);
-
- Memory = Marshal.AllocHGlobal(PartyFinder.PacketInfo.PacketSize);
-
- this.receiveListingHook = new Hook(Address.ReceiveListing, new ReceiveListingDelegate(HandleReceiveListingDetour));
- }
-
- public void Enable() {
+ ///
+ /// Enables this module.
+ ///
+ public void Enable()
+ {
this.receiveListingHook.Enable();
}
- public void Dispose() {
+ ///
+ /// Dispose of m anaged and unmanaged resources.
+ ///
+ public void Dispose()
+ {
this.receiveListingHook.Dispose();
- Marshal.FreeHGlobal(Memory);
+ Marshal.FreeHGlobal(this.memory);
}
- private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) {
- try {
- HandleListingEvents(data);
- } catch (Exception ex) {
+ private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
+ {
+ try
+ {
+ this.HandleListingEvents(data);
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Exception on ReceiveListing hook.");
}
this.receiveListingHook.Original(managerPtr, data);
}
- private void HandleListingEvents(IntPtr data) {
+ private void HandleListingEvents(IntPtr data)
+ {
var dataPtr = data + 0x10;
var packet = Marshal.PtrToStructure(dataPtr);
@@ -73,51 +92,66 @@ namespace Dalamud.Game.Internal.Gui {
// rewriting is an expensive operation, so only do it if necessary
var needToRewrite = false;
- for (var i = 0; i < packet.listings.Length; i++) {
+ for (var i = 0; i < packet.Listings.Length; i++)
+ {
// these are empty slots that are not shown to the player
- if (packet.listings[i].IsNull()) {
+ if (packet.Listings[i].IsNull())
+ {
continue;
}
- var listing = new PartyFinderListing(packet.listings[i], Dalamud.Data, Dalamud.SeStringManager);
- var args = new PartyFinderListingEventArgs(packet.batchNumber);
- ReceiveListing?.Invoke(listing, args);
+ var listing = new PartyFinderListing(packet.Listings[i], this.dalamud.Data, this.dalamud.SeStringManager);
+ var args = new PartyFinderListingEventArgs(packet.BatchNumber);
+ this.ReceiveListing?.Invoke(listing, args);
- if (args.Visible) {
+ if (args.Visible)
+ {
continue;
}
// hide the listing from the player by setting it to a null listing
- packet.listings[i] = new PartyFinder.Listing();
+ packet.Listings[i] = default;
needToRewrite = true;
}
- if (!needToRewrite) {
+ if (!needToRewrite)
+ {
return;
}
// write our struct into the memory (doing this directly crashes the game)
- Marshal.StructureToPtr(packet, Memory, false);
+ Marshal.StructureToPtr(packet, this.memory, false);
// copy our new memory over the game's
- unsafe {
- Buffer.MemoryCopy(
- (void*) Memory,
- (void*) dataPtr,
- PartyFinder.PacketInfo.PacketSize,
- PartyFinder.PacketInfo.PacketSize
- );
+ unsafe
+ {
+ Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinder.PacketInfo.PacketSize, PartyFinder.PacketInfo.PacketSize);
}
}
}
- public class PartyFinderListingEventArgs {
+ ///
+ /// This class represents additional arguments passed by the game.
+ ///
+ public class PartyFinderListingEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The batch number.
+ internal PartyFinderListingEventArgs(int batchNumber)
+ {
+ this.BatchNumber = batchNumber;
+ }
+
+ ///
+ /// Gets the batch number.
+ ///
public int BatchNumber { get; }
+ ///
+ /// Gets or sets a value indicating whether the listing is visible.
+ ///
public bool Visible { get; set; } = true;
-
- internal PartyFinderListingEventArgs(int batchNumber) {
- BatchNumber = batchNumber;
- }
}
}
diff --git a/Dalamud/Game/Internal/Gui/Structs/Addon.cs b/Dalamud/Game/Internal/Gui/Structs/Addon.cs
index 0a93b2351..be1f08f33 100644
--- a/Dalamud/Game/Internal/Gui/Structs/Addon.cs
+++ b/Dalamud/Game/Internal/Gui/Structs/Addon.cs
@@ -1,8 +1,59 @@
using System.Runtime.InteropServices;
-namespace Dalamud.Game.Internal.Gui.Structs {
+namespace Dalamud.Game.Internal.Gui.Structs
+{
+ ///
+ /// Native memory representation of an FFXIV UI addon.
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ public struct Addon
+ {
+ ///
+ /// The name of the addon.
+ ///
+ [FieldOffset(AddonOffsets.Name)]
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
+ public string Name;
- public class AddonOffsets {
+ ///
+ /// Various flags that can be set on the addon.
+ ///
+ ///
+ /// This is a bitfield.
+ ///
+ [FieldOffset(AddonOffsets.Flags)]
+ public byte Flags;
+
+ ///
+ /// The X position of the addon on screen.
+ ///
+ [FieldOffset(AddonOffsets.X)]
+ public short X;
+
+ ///
+ /// The Y position of the addon on screen.
+ ///
+ [FieldOffset(AddonOffsets.Y)]
+ public short Y;
+
+ ///
+ /// The scale of the addon.
+ ///
+ [FieldOffset(AddonOffsets.Scale)]
+ public float Scale;
+
+ ///
+ /// The root node of the addon's node tree.
+ ///
+ [FieldOffset(AddonOffsets.RootNode)]
+ public unsafe AtkResNode* RootNode;
+ }
+
+ ///
+ /// Memory offsets for the type.
+ ///
+ public static class AddonOffsets
+ {
public const int Name = 0x8;
public const int RootNode = 0xC8;
public const int Flags = 0x182;
@@ -10,17 +61,4 @@ namespace Dalamud.Game.Internal.Gui.Structs {
public const int Y = 0x1BE;
public const int Scale = 0x1AC;
}
-
- [StructLayout(LayoutKind.Explicit)]
- public struct Addon {
- [FieldOffset(AddonOffsets.Name), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
- public string Name;
-
- [FieldOffset(AddonOffsets.Flags)] public byte Flags;
- [FieldOffset(AddonOffsets.X)] public short X;
- [FieldOffset(AddonOffsets.Y)] public short Y;
- [FieldOffset(AddonOffsets.Scale)] public float Scale;
- [FieldOffset(AddonOffsets.RootNode)] public unsafe AtkResNode* RootNode;
-
- }
}
diff --git a/Dalamud/Game/Internal/Gui/Structs/AtkResNode.cs b/Dalamud/Game/Internal/Gui/Structs/AtkResNode.cs
index 1c9b2dfba..406a1262a 100644
--- a/Dalamud/Game/Internal/Gui/Structs/AtkResNode.cs
+++ b/Dalamud/Game/Internal/Gui/Structs/AtkResNode.cs
@@ -1,49 +1,130 @@
using System;
using System.Runtime.InteropServices;
-namespace Dalamud.Game.Internal.Gui.Structs {
-
+namespace Dalamud.Game.Internal.Gui.Structs
+{
+ ///
+ /// Native memory representation of a UI resource node.
+ ///
+ ///
+ /// This is copied from https://github.com/aers/FFXIVClientStructs/blob/main/Component/GUI/AtkResNode.cs.
+ /// If you need newer features, include FFXIVClientStructs and ILMerge the assembly.
+ ///
[StructLayout(LayoutKind.Explicit, Size = 0xA8)]
+ public unsafe struct AtkResNode
+ {
+ [FieldOffset(0x0)]
+ public IntPtr AtkEventTarget;
- // https://github.com/aers/FFXIVClientStructs/blob/main/Component/GUI/AtkResNode.cs
- public unsafe struct AtkResNode {
- [FieldOffset(0x0)] public IntPtr AtkEventTarget;
- [FieldOffset(0x8)] public uint NodeID;
- [FieldOffset(0x20)] public AtkResNode* ParentNode;
- [FieldOffset(0x28)] public AtkResNode* PrevSiblingNode;
- [FieldOffset(0x30)] public AtkResNode* NextSiblingNode;
- [FieldOffset(0x38)] public AtkResNode* ChildNode;
- [FieldOffset(0x40)] public ushort Type;
- [FieldOffset(0x42)] public ushort ChildCount;
- [FieldOffset(0x44)] public float X;
- [FieldOffset(0x48)] public float Y;
- [FieldOffset(0x4C)] public float ScaleX;
- [FieldOffset(0x50)] public float ScaleY;
- [FieldOffset(0x54)] public float Rotation;
- [FieldOffset(0x58)] public fixed float UnkMatrix[3 * 2];
- [FieldOffset(0x70)] public uint Color;
- [FieldOffset(0x74)] public float Depth;
- [FieldOffset(0x78)] public float Depth_2;
- [FieldOffset(0x7C)] public ushort AddRed;
- [FieldOffset(0x7E)] public ushort AddGreen;
- [FieldOffset(0x80)] public ushort AddBlue;
- [FieldOffset(0x82)] public ushort AddRed_2;
- [FieldOffset(0x84)] public ushort AddGreen_2;
- [FieldOffset(0x86)] public ushort AddBlue_2;
- [FieldOffset(0x88)] public byte MultiplyRed;
- [FieldOffset(0x89)] public byte MultiplyGreen;
- [FieldOffset(0x8A)] public byte MultiplyBlue;
- [FieldOffset(0x8B)] public byte MultiplyRed_2;
- [FieldOffset(0x8C)] public byte MultiplyGreen_2;
- [FieldOffset(0x8D)] public byte MultiplyBlue_2;
- [FieldOffset(0x8E)] public byte Alpha_2;
- [FieldOffset(0x8F)] public byte UnkByte_1;
- [FieldOffset(0x90)] public ushort Width;
- [FieldOffset(0x92)] public ushort Height;
- [FieldOffset(0x94)] public float OriginX;
- [FieldOffset(0x98)] public float OriginY;
- [FieldOffset(0x9C)] public ushort Priority;
- [FieldOffset(0x9E)] public short Flags;
- [FieldOffset(0xA0)] public uint Flags_2;
+ [FieldOffset(0x8)]
+ public uint NodeID;
+
+ [FieldOffset(0x20)]
+ public AtkResNode* ParentNode;
+
+ [FieldOffset(0x28)]
+ public AtkResNode* PrevSiblingNode;
+
+ [FieldOffset(0x30)]
+ public AtkResNode* NextSiblingNode;
+
+ [FieldOffset(0x38)]
+ public AtkResNode* ChildNode;
+
+ [FieldOffset(0x40)]
+ public ushort Type;
+
+ [FieldOffset(0x42)]
+ public ushort ChildCount;
+
+ [FieldOffset(0x44)]
+ public float X;
+
+ [FieldOffset(0x48)]
+ public float Y;
+
+ [FieldOffset(0x4C)]
+ public float ScaleX;
+
+ [FieldOffset(0x50)]
+ public float ScaleY;
+
+ [FieldOffset(0x54)]
+ public float Rotation;
+
+ [FieldOffset(0x58)]
+ public fixed float UnkMatrix[3 * 2];
+
+ [FieldOffset(0x70)]
+ public uint Color;
+
+ [FieldOffset(0x74)]
+ public float Depth;
+
+ [FieldOffset(0x78)]
+ public float Depth_2;
+
+ [FieldOffset(0x7C)]
+ public ushort AddRed;
+
+ [FieldOffset(0x7E)]
+ public ushort AddGreen;
+
+ [FieldOffset(0x80)]
+ public ushort AddBlue;
+
+ [FieldOffset(0x82)]
+ public ushort AddRed_2;
+
+ [FieldOffset(0x84)]
+ public ushort AddGreen_2;
+
+ [FieldOffset(0x86)]
+ public ushort AddBlue_2;
+
+ [FieldOffset(0x88)]
+ public byte MultiplyRed;
+
+ [FieldOffset(0x89)]
+ public byte MultiplyGreen;
+
+ [FieldOffset(0x8A)]
+ public byte MultiplyBlue;
+
+ [FieldOffset(0x8B)]
+ public byte MultiplyRed_2;
+
+ [FieldOffset(0x8C)]
+ public byte MultiplyGreen_2;
+
+ [FieldOffset(0x8D)]
+ public byte MultiplyBlue_2;
+
+ [FieldOffset(0x8E)]
+ public byte Alpha_2;
+
+ [FieldOffset(0x8F)]
+ public byte UnkByte_1;
+
+ [FieldOffset(0x90)]
+ public ushort Width;
+
+ [FieldOffset(0x92)]
+ public ushort Height;
+
+ [FieldOffset(0x94)]
+ public float OriginX;
+
+ [FieldOffset(0x98)]
+ public float OriginY;
+
+ [FieldOffset(0x9C)]
+ public ushort Priority;
+
+ [FieldOffset(0x9E)]
+ public short Flags;
+
+ [FieldOffset(0xA0)]
+ public uint Flags_2;
}
}
diff --git a/Dalamud/Game/Internal/Gui/Structs/PartyFinder.cs b/Dalamud/Game/Internal/Gui/Structs/PartyFinder.cs
index 3a61caf3e..8fe3204b0 100755
--- a/Dalamud/Game/Internal/Gui/Structs/PartyFinder.cs
+++ b/Dalamud/Game/Internal/Gui/Structs/PartyFinder.cs
@@ -1,455 +1,129 @@
-using System;
-using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
-using Dalamud.Data;
-using Dalamud.Game.Text.SeStringHandling;
-using Lumina.Excel.GeneratedSheets;
-
-namespace Dalamud.Game.Internal.Gui.Structs {
- #region Raw structs
-
- internal static class PartyFinder {
- public static class PacketInfo {
- public static readonly int PacketSize = Marshal.SizeOf();
- }
+namespace Dalamud.Game.Internal.Gui.Structs
+{
+ ///
+ /// PartyFinder related network structs and static constants.
+ ///
+ internal static class PartyFinder
+ {
+ ///
+ /// The structure of the PartyFinder packet.
+ ///
[StructLayout(LayoutKind.Sequential)]
- public readonly struct Packet {
- public readonly int batchNumber;
+ internal readonly struct Packet
+ {
+ internal readonly int BatchNumber;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
private readonly byte[] padding1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
- public readonly Listing[] listings;
+ internal readonly Listing[] Listings;
}
+ ///
+ /// The structure of an individual listing within a packet.
+ ///
[StructLayout(LayoutKind.Sequential)]
- public readonly struct Listing {
+ internal readonly struct Listing
+ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
private readonly byte[] header1;
- internal readonly uint id;
+ internal readonly uint Id;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
private readonly byte[] header2;
- internal readonly uint contentIdLower;
+ internal readonly uint ContentIdLower;
private readonly ushort unknownShort1;
private readonly ushort unknownShort2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
private readonly byte[] header3;
- internal readonly byte category;
+ internal readonly byte Category;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
private readonly byte[] header4;
- internal readonly ushort duty;
- internal readonly byte dutyType;
+ internal readonly ushort Duty;
+ internal readonly byte DutyType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
private readonly byte[] header5;
- internal readonly ushort world;
+ internal readonly ushort World;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
private readonly byte[] header6;
- internal readonly byte objective;
- internal readonly byte beginnersWelcome;
- internal readonly byte conditions;
- internal readonly byte dutyFinderSettings;
- internal readonly byte lootRules;
+ internal readonly byte Objective;
+ internal readonly byte BeginnersWelcome;
+ internal readonly byte Conditions;
+ internal readonly byte DutyFinderSettings;
+ internal readonly byte LootRules;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
private readonly byte[] header7; // all zero in every pf I've examined
private readonly uint lastPatchHotfixTimestamp; // last time the servers were restarted?
- internal readonly ushort secondsRemaining;
+ internal readonly ushort SecondsRemaining;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
- internal readonly ushort minimumItemLevel;
- internal readonly ushort homeWorld;
- internal readonly ushort currentWorld;
+ internal readonly ushort MinimumItemLevel;
+ internal readonly ushort HomeWorld;
+ internal readonly ushort CurrentWorld;
private readonly byte header9;
- internal readonly byte numSlots;
+ internal readonly byte NumSlots;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
private readonly byte[] header10;
- internal readonly byte searchArea;
+ internal readonly byte SearchArea;
private readonly byte header11;
- internal readonly byte numParties;
+ internal readonly byte NumParties;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
- internal readonly uint[] slots;
+ internal readonly uint[] Slots;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
- internal readonly byte[] jobsPresent;
+ internal readonly byte[] JobsPresent;
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
- internal readonly byte[] name;
+ internal readonly byte[] Name;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
- internal readonly byte[] description;
+ internal readonly byte[] Description;
- internal bool IsNull() {
+ internal bool IsNull()
+ {
// a valid party finder must have at least one slot set
- return this.slots.All(slot => slot == 0);
- }
- }
- }
-
- #endregion
-
- #region Read-only classes
-
- public class PartyFinderListing {
- ///
- /// The ID assigned to this listing by the game's server.
- ///
- public uint Id { get; }
- ///
- /// The lower bits of the player's content ID.
- ///
- public uint ContentIdLower { get; }
- ///
- /// The name of the player hosting this listing.
- ///
- public SeString Name { get; }
- ///
- /// The description of this listing as set by the host. May be multiple lines.
- ///
- public SeString Description { get; }
- ///
- /// The world that this listing was created on.
- ///
- public Lazy World { get; }
- ///
- /// The home world of the listing's host.
- ///
- public Lazy HomeWorld { get; }
- ///
- /// The current world of the listing's host.
- ///
- public Lazy CurrentWorld { get; }
- ///
- /// The Party Finder category this listing is listed under.
- ///
- public Category Category { get; }
- ///
- /// The row ID of the duty this listing is for. May be 0 for non-duty listings.
- ///
- public ushort RawDuty { get; }
- ///
- /// The duty this listing is for. May be null for non-duty listings.
- ///
- public Lazy Duty { get; }
- ///
- /// The type of duty this listing is for.
- ///
- public DutyType DutyType { get; }
- ///
- /// If this listing is beginner-friendly. Shown with a sprout icon in-game.
- ///
- public bool BeginnersWelcome { get; }
- ///
- /// How many seconds this listing will continue to be available for. It may end before this time if the party
- /// fills or the host ends it early.
- ///
- public ushort SecondsRemaining { get; }
- ///
- /// The minimum item level required to join this listing.
- ///
- public ushort MinimumItemLevel { get; }
- ///
- /// The number of parties this listing is recruiting for.
- ///
- public byte Parties { get; }
- ///
- /// The number of player slots this listing is recruiting for.
- ///
- public byte SlotsAvailable { get; }
-
- ///
- /// A list of player slots that the Party Finder is accepting.
- ///
- public IReadOnlyCollection Slots => this.slots;
-
- ///
- /// The objective of this listing.
- ///
- public ObjectiveFlags Objective => (ObjectiveFlags) this.objective;
-
- ///
- /// The conditions of this listing.
- ///
- public ConditionFlags Conditions => (ConditionFlags) this.conditions;
-
- ///
- /// The Duty Finder settings that will be used for this listing.
- ///
- public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags) this.dutyFinderSettings;
-
- ///
- /// The loot rules that will be used for this listing.
- ///
- public LootRuleFlags LootRules => (LootRuleFlags) this.lootRules;
-
- ///
- /// Where this listing is searching. Note that this is also used for denoting alliance raid listings and one
- /// player per job.
- ///
- public SearchAreaFlags SearchArea => (SearchAreaFlags) this.searchArea;
-
- ///
- /// A list of the class/job IDs that are currently present in the party.
- ///
- public IReadOnlyCollection RawJobsPresent => this.jobsPresent;
- ///
- /// A list of the classes/jobs that are currently present in the party.
- ///
- public IReadOnlyCollection> JobsPresent { get; }
-
- #region Backing fields
-
- private readonly byte objective;
- private readonly byte conditions;
- private readonly byte dutyFinderSettings;
- private readonly byte lootRules;
- private readonly byte searchArea;
- private readonly PartyFinderSlot[] slots;
- private readonly byte[] jobsPresent;
-
- #endregion
-
- #region Indexers
-
- public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint) flag) > 0;
-
- public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint) flag) > 0;
-
- public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint) flag) > 0;
-
- public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint) flag) > 0;
-
- public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint) flag) > 0;
-
- #endregion
-
- internal PartyFinderListing(PartyFinder.Listing listing, DataManager dataManager, SeStringManager seStringManager) {
- this.objective = listing.objective;
- this.conditions = listing.conditions;
- this.dutyFinderSettings = listing.dutyFinderSettings;
- this.lootRules = listing.lootRules;
- this.searchArea = listing.searchArea;
- this.slots = listing.slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray();
- this.jobsPresent = listing.jobsPresent;
-
- Id = listing.id;
- ContentIdLower = listing.contentIdLower;
- Name = seStringManager.Parse(listing.name.TakeWhile(b => b != 0).ToArray());
- Description = seStringManager.Parse(listing.description.TakeWhile(b => b != 0).ToArray());
- World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.world));
- HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.homeWorld));
- CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.currentWorld));
- Category = (Category) listing.category;
- RawDuty = listing.duty;
- Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.duty));
- DutyType = (DutyType) listing.dutyType;
- BeginnersWelcome = listing.beginnersWelcome == 1;
- SecondsRemaining = listing.secondsRemaining;
- MinimumItemLevel = listing.minimumItemLevel;
- Parties = listing.numParties;
- SlotsAvailable = listing.numSlots;
- JobsPresent = listing.jobsPresent
- .Select(id => new Lazy(() => id == 0
- ? null
- : dataManager.GetExcelSheet().GetRow(id)))
- .ToArray();
- }
- }
-
- ///
- /// A player slot in a Party Finder listing.
- ///
- public class PartyFinderSlot {
- private readonly uint accepting;
- private JobFlags[] listAccepting;
-
- ///
- /// List of jobs that this slot is accepting.
- ///
- public IReadOnlyCollection Accepting {
- get {
- if (this.listAccepting != null) {
- return this.listAccepting;
- }
-
- this.listAccepting = Enum.GetValues(typeof(JobFlags))
- .Cast()
- .Where(flag => this[flag])
- .ToArray();
-
- return this.listAccepting;
+ return this.Slots.All(slot => slot == 0);
}
}
///
- /// Tests if this slot is accepting a job.
+ /// PartyFinder packet constants.
///
- /// Job to test
- public bool this[JobFlags flag] => (this.accepting & (uint) flag) > 0;
-
- internal PartyFinderSlot(uint accepting) {
- this.accepting = accepting;
+ public static class PacketInfo
+ {
+ ///
+ /// The size of the PartyFinder packet.
+ ///
+ public static readonly int PacketSize = Marshal.SizeOf();
}
}
-
- [Flags]
- public enum SearchAreaFlags : uint {
- DataCentre = 1 << 0,
- Private = 1 << 1,
- AllianceRaid = 1 << 2,
- World = 1 << 3,
- OnePlayerPerJob = 1 << 5,
- }
-
- [Flags]
- public enum JobFlags {
- Gladiator = 1 << 1,
- Pugilist = 1 << 2,
- Marauder = 1 << 3,
- Lancer = 1 << 4,
- Archer = 1 << 5,
- Conjurer = 1 << 6,
- Thaumaturge = 1 << 7,
- Paladin = 1 << 8,
- Monk = 1 << 9,
- Warrior = 1 << 10,
- Dragoon = 1 << 11,
- Bard = 1 << 12,
- WhiteMage = 1 << 13,
- BlackMage = 1 << 14,
- Arcanist = 1 << 15,
- Summoner = 1 << 16,
- Scholar = 1 << 17,
- Rogue = 1 << 18,
- Ninja = 1 << 19,
- Machinist = 1 << 20,
- DarkKnight = 1 << 21,
- Astrologian = 1 << 22,
- Samurai = 1 << 23,
- RedMage = 1 << 24,
- BlueMage = 1 << 25,
- Gunbreaker = 1 << 26,
- Dancer = 1 << 27,
- }
-
- public static class JobFlagsExt {
- ///
- /// Get the actual ClassJob from the in-game sheets for this JobFlags.
- ///
- /// A JobFlags enum member
- /// A DataManager to get the ClassJob from
- /// A ClassJob if found or null if not
- public static ClassJob ClassJob(this JobFlags job, DataManager data) {
- var jobs = data.GetExcelSheet();
-
- uint? row = job switch {
- JobFlags.Gladiator => 1,
- JobFlags.Pugilist => 2,
- JobFlags.Marauder => 3,
- JobFlags.Lancer => 4,
- JobFlags.Archer => 5,
- JobFlags.Conjurer => 6,
- JobFlags.Thaumaturge => 7,
- JobFlags.Paladin => 19,
- JobFlags.Monk => 20,
- JobFlags.Warrior => 21,
- JobFlags.Dragoon => 22,
- JobFlags.Bard => 23,
- JobFlags.WhiteMage => 24,
- JobFlags.BlackMage => 25,
- JobFlags.Arcanist => 26,
- JobFlags.Summoner => 27,
- JobFlags.Scholar => 28,
- JobFlags.Rogue => 29,
- JobFlags.Ninja => 30,
- JobFlags.Machinist => 31,
- JobFlags.DarkKnight => 32,
- JobFlags.Astrologian => 33,
- JobFlags.Samurai => 34,
- JobFlags.RedMage => 35,
- JobFlags.BlueMage => 36,
- JobFlags.Gunbreaker => 37,
- JobFlags.Dancer => 38,
- _ => null,
- };
-
- return row == null ? null : jobs.GetRow((uint) row);
- }
- }
-
- [Flags]
- public enum ObjectiveFlags : uint {
- None = 0,
- DutyCompletion = 1,
- Practice = 2,
- Loot = 4,
- }
-
- [Flags]
- public enum ConditionFlags : uint {
- None = 1,
- DutyComplete = 2,
- DutyIncomplete = 4,
- }
-
- [Flags]
- public enum DutyFinderSettingsFlags : uint {
- None = 0,
- UndersizedParty = 1 << 0,
- MinimumItemLevel = 1 << 1,
- SilenceEcho = 1 << 2,
- }
-
- [Flags]
- public enum LootRuleFlags : uint {
- None = 0,
- GreedOnly = 1,
- Lootmaster = 2,
- }
-
- public enum Category {
- Duty = 0,
- QuestBattles = 1 << 0,
- Fates = 1 << 1,
- TreasureHunt = 1 << 2,
- TheHunt = 1 << 3,
- GatheringForays = 1 << 4,
- DeepDungeons = 1 << 5,
- AdventuringForays = 1 << 6,
- }
-
- public enum DutyType {
- Other = 0,
- Roulette = 1 << 0,
- Normal = 1 << 1,
- }
-
- #endregion
}
diff --git a/Dalamud/Game/Internal/Gui/Structs/PartyFinderListing.cs b/Dalamud/Game/Internal/Gui/Structs/PartyFinderListing.cs
new file mode 100644
index 000000000..b533a9741
--- /dev/null
+++ b/Dalamud/Game/Internal/Gui/Structs/PartyFinderListing.cs
@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Dalamud.Data;
+using Dalamud.Game.Text.SeStringHandling;
+using Lumina.Excel.GeneratedSheets;
+
+namespace Dalamud.Game.Internal.Gui.Structs
+{
+ ///
+ /// A single listing in party finder.
+ ///
+ public class PartyFinderListing
+ {
+ #region Backing fields
+
+ private readonly byte objective;
+ private readonly byte conditions;
+ private readonly byte dutyFinderSettings;
+ private readonly byte lootRules;
+ private readonly byte searchArea;
+ private readonly PartyFinderSlot[] slots;
+ private readonly byte[] jobsPresent;
+
+ #endregion
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The interop listing data.
+ /// The DataManager instance.
+ /// The SeStringManager instance.
+ internal PartyFinderListing(PartyFinder.Listing listing, DataManager dataManager, SeStringManager seStringManager)
+ {
+ this.objective = listing.Objective;
+ this.conditions = listing.Conditions;
+ this.dutyFinderSettings = listing.DutyFinderSettings;
+ this.lootRules = listing.LootRules;
+ this.searchArea = listing.SearchArea;
+ this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray();
+ this.jobsPresent = listing.JobsPresent;
+
+ this.Id = listing.Id;
+ this.ContentIdLower = listing.ContentIdLower;
+ this.Name = seStringManager.Parse(listing.Name.TakeWhile(b => b != 0).ToArray());
+ this.Description = seStringManager.Parse(listing.Description.TakeWhile(b => b != 0).ToArray());
+ this.World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.World));
+ this.HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.HomeWorld));
+ this.CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.CurrentWorld));
+ this.Category = (Category)listing.Category;
+ this.RawDuty = listing.Duty;
+ this.Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.Duty));
+ this.DutyType = (DutyType)listing.DutyType;
+ this.BeginnersWelcome = listing.BeginnersWelcome == 1;
+ this.SecondsRemaining = listing.SecondsRemaining;
+ this.MinimumItemLevel = listing.MinimumItemLevel;
+ this.Parties = listing.NumParties;
+ this.SlotsAvailable = listing.NumSlots;
+ this.JobsPresent = listing.JobsPresent
+ .Select(id => new Lazy(
+ () => id == 0
+ ? null
+ : dataManager.GetExcelSheet().GetRow(id)))
+ .ToArray();
+ }
+
+ ///
+ /// Gets the ID assigned to this listing by the game's server.
+ ///
+ public uint Id { get; }
+
+ ///
+ /// Gets the lower bits of the player's content ID.
+ ///
+ public uint ContentIdLower { get; }
+
+ ///
+ /// Gets the name of the player hosting this listing.
+ ///
+ public SeString Name { get; }
+
+ ///
+ /// Gets the description of this listing as set by the host. May be multiple lines.
+ ///
+ public SeString Description { get; }
+
+ ///
+ /// Gets the world that this listing was created on.
+ ///
+ public Lazy World { get; }
+
+ ///
+ /// Gets the home world of the listing's host.
+ ///
+ public Lazy HomeWorld { get; }
+
+ ///
+ /// Gets the current world of the listing's host.
+ ///
+ public Lazy CurrentWorld { get; }
+
+ ///
+ /// Gets the Party Finder category this listing is listed under.
+ ///
+ public Category Category { get; }
+
+ ///
+ /// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings.
+ ///
+ public ushort RawDuty { get; }
+
+ ///
+ /// Gets the duty this listing is for. May be null for non-duty listings.
+ ///
+ public Lazy Duty { get; }
+
+ ///
+ /// Gets the type of duty this listing is for.
+ ///
+ public DutyType DutyType { get; }
+
+ ///
+ /// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game.
+ ///
+ public bool BeginnersWelcome { get; }
+
+ ///
+ /// Gets how many seconds this listing will continue to be available for. It may end before this time if the party
+ /// fills or the host ends it early.
+ ///
+ public ushort SecondsRemaining { get; }
+
+ ///
+ /// Gets the minimum item level required to join this listing.
+ ///
+ public ushort MinimumItemLevel { get; }
+
+ ///
+ /// Gets the number of parties this listing is recruiting for.
+ ///
+ public byte Parties { get; }
+
+ ///
+ /// Gets the number of player slots this listing is recruiting for.
+ ///
+ public byte SlotsAvailable { get; }
+
+ ///
+ /// Gets a list of player slots that the Party Finder is accepting.
+ ///
+ public IReadOnlyCollection Slots => this.slots;
+
+ ///
+ /// Gets the objective of this listing.
+ ///
+ public ObjectiveFlags Objective => (ObjectiveFlags)this.objective;
+
+ ///
+ /// Gets the conditions of this listing.
+ ///
+ public ConditionFlags Conditions => (ConditionFlags)this.conditions;
+
+ ///
+ /// Gets the Duty Finder settings that will be used for this listing.
+ ///
+ public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings;
+
+ ///
+ /// Gets the loot rules that will be used for this listing.
+ ///
+ public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules;
+
+ ///
+ /// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one
+ /// player per job.
+ ///
+ public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea;
+
+ ///
+ /// Gets a list of the class/job IDs that are currently present in the party.
+ ///
+ public IReadOnlyCollection RawJobsPresent => this.jobsPresent;
+
+ ///
+ /// Gets a list of the classes/jobs that are currently present in the party.
+ ///
+ public IReadOnlyCollection> JobsPresent { get; }
+
+ #region Indexers
+
+ ///
+ /// Check if the given flag is present.
+ ///
+ /// The flag to check for.
+ /// A value indicating whether the flag is present.
+ public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0;
+
+ ///
+ /// Check if the given flag is present.
+ ///
+ /// The flag to check for.
+ /// A value indicating whether the flag is present.
+ public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0;
+
+ ///
+ /// Check if the given flag is present.
+ ///
+ /// The flag to check for.
+ /// A value indicating whether the flag is present.
+ public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0;
+
+ ///
+ /// Check if the given flag is present.
+ ///
+ /// The flag to check for.
+ /// A value indicating whether the flag is present.
+ public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0;
+
+ ///
+ /// Check if the given flag is present.
+ ///
+ /// The flag to check for.
+ /// A value indicating whether the flag is present.
+ public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0;
+
+ #endregion
+
+ }
+}
diff --git a/Dalamud/Game/Internal/Gui/Structs/PartyFinderSlot.cs b/Dalamud/Game/Internal/Gui/Structs/PartyFinderSlot.cs
new file mode 100644
index 000000000..02b549842
--- /dev/null
+++ b/Dalamud/Game/Internal/Gui/Structs/PartyFinderSlot.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Dalamud.Game.Internal.Gui.Structs
+{
+ ///
+ /// A player slot in a Party Finder listing.
+ ///
+ public class PartyFinderSlot
+ {
+ private readonly uint accepting;
+ private JobFlags[] listAccepting;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The flag value of accepted jobs.
+ internal PartyFinderSlot(uint accepting)
+ {
+ this.accepting = accepting;
+ }
+
+ ///
+ /// Gets a list of jobs that this slot is accepting.
+ ///
+ public IReadOnlyCollection Accepting
+ {
+ get
+ {
+ if (this.listAccepting != null)
+ {
+ return this.listAccepting;
+ }
+
+ this.listAccepting = Enum.GetValues(typeof(JobFlags))
+ .Cast()
+ .Where(flag => this[flag])
+ .ToArray();
+
+ return this.listAccepting;
+ }
+ }
+
+ ///
+ /// Tests if this slot is accepting a job.
+ ///
+ /// Job to test.
+ public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0;
+ }
+}
diff --git a/Dalamud/Game/Internal/Gui/Structs/PartyFinderTypes.cs b/Dalamud/Game/Internal/Gui/Structs/PartyFinderTypes.cs
new file mode 100644
index 000000000..492b66a61
--- /dev/null
+++ b/Dalamud/Game/Internal/Gui/Structs/PartyFinderTypes.cs
@@ -0,0 +1,397 @@
+using System;
+
+using Dalamud.Data;
+using Lumina.Excel.GeneratedSheets;
+
+namespace Dalamud.Game.Internal.Gui.Structs
+{
+ ///
+ /// Search area flags for the class.
+ ///
+ [Flags]
+ public enum SearchAreaFlags : uint
+ {
+ ///
+ /// Datacenter.
+ ///
+ DataCentre = 1 << 0,
+
+ ///
+ /// Private.
+ ///
+ Private = 1 << 1,
+
+ ///
+ /// Alliance raid.
+ ///
+ AllianceRaid = 1 << 2,
+
+ ///
+ /// World.
+ ///
+ World = 1 << 3,
+
+ ///
+ /// One player per job.
+ ///
+ OnePlayerPerJob = 1 << 5,
+ }
+
+ ///
+ /// Job flags for the class.
+ ///
+ [Flags]
+ public enum JobFlags
+ {
+ ///
+ /// Gladiator (GLD).
+ ///
+ Gladiator = 1 << 1,
+
+ ///
+ /// Pugilist (PGL).
+ ///
+ Pugilist = 1 << 2,
+
+ ///
+ /// Marauder (MRD).
+ ///
+ Marauder = 1 << 3,
+
+ ///
+ /// Lancer (LNC).
+ ///
+ Lancer = 1 << 4,
+
+ ///
+ /// Archer (ARC).
+ ///
+ Archer = 1 << 5,
+
+ ///
+ /// Conjurer (CNJ).
+ ///
+ Conjurer = 1 << 6,
+
+ ///
+ /// Thaumaturge (THM).
+ ///
+ Thaumaturge = 1 << 7,
+
+ ///
+ /// Paladin (PLD).
+ ///
+ Paladin = 1 << 8,
+
+ ///
+ /// Monk (MNK).
+ ///
+ Monk = 1 << 9,
+
+ ///
+ /// Warrior (WAR).
+ ///
+ Warrior = 1 << 10,
+
+ ///
+ /// Dragoon (DRG).
+ ///
+ Dragoon = 1 << 11,
+
+ ///
+ /// Bard (BRD).
+ ///
+ Bard = 1 << 12,
+
+ ///
+ /// White mage (WHM).
+ ///
+ WhiteMage = 1 << 13,
+
+ ///
+ /// Black mage (BLM).
+ ///
+ BlackMage = 1 << 14,
+
+ ///
+ /// Arcanist (ACN).
+ ///
+ Arcanist = 1 << 15,
+
+ ///
+ /// Summoner (SMN).
+ ///
+ Summoner = 1 << 16,
+
+ ///
+ /// Scholar (SCH).
+ ///
+ Scholar = 1 << 17,
+
+ ///
+ /// Rogue (ROG).
+ ///
+ Rogue = 1 << 18,
+
+ ///
+ /// Ninja (NIN).
+ ///
+ Ninja = 1 << 19,
+
+ ///
+ /// Machinist (MCH).
+ ///
+ Machinist = 1 << 20,
+
+ ///
+ /// Dark Knight (DRK).
+ ///
+ DarkKnight = 1 << 21,
+
+ ///
+ /// Astrologian (AST).
+ ///
+ Astrologian = 1 << 22,
+
+ ///
+ /// Samurai (SAM).
+ ///
+ Samurai = 1 << 23,
+
+ ///
+ /// Red mage (RDM).
+ ///
+ RedMage = 1 << 24,
+
+ ///
+ /// Blue mage (BLM).
+ ///
+ BlueMage = 1 << 25,
+
+ ///
+ /// Gunbreaker (GNB).
+ ///
+ Gunbreaker = 1 << 26,
+
+ ///
+ /// Dancer (DNC).
+ ///
+ Dancer = 1 << 27,
+ }
+
+ ///
+ /// Objective flags for the class.
+ ///
+ [Flags]
+ public enum ObjectiveFlags : uint
+ {
+ ///
+ /// No objective.
+ ///
+ None = 0,
+
+ ///
+ /// The duty completion objective.
+ ///
+ DutyCompletion = 1,
+
+ ///
+ /// The practice objective.
+ ///
+ Practice = 2,
+
+ ///
+ /// The loot objective.
+ ///
+ Loot = 4,
+ }
+
+ ///
+ /// Condition flags for the class.
+ ///
+ [Flags]
+ public enum ConditionFlags : uint
+ {
+ ///
+ /// No duty condition.
+ ///
+ None = 1,
+
+ ///
+ /// The duty complete condition.
+ ///
+ DutyComplete = 2,
+
+ ///
+ /// The duty incomplete condition.
+ ///
+ DutyIncomplete = 4,
+ }
+
+ ///
+ /// Duty finder settings flags for the class.
+ ///
+ [Flags]
+ public enum DutyFinderSettingsFlags : uint
+ {
+ ///
+ /// No duty finder settings.
+ ///
+ None = 0,
+
+ ///
+ /// The undersized party setting.
+ ///
+ UndersizedParty = 1 << 0,
+
+ ///
+ /// The minimum item level setting.
+ ///
+ MinimumItemLevel = 1 << 1,
+
+ ///
+ /// The silence echo setting.
+ ///
+ SilenceEcho = 1 << 2,
+ }
+
+ ///
+ /// Loot rule flags for the class.
+ ///
+ [Flags]
+ public enum LootRuleFlags : uint
+ {
+ ///
+ /// No loot rules.
+ ///
+ None = 0,
+
+ ///
+ /// The greed only rule.
+ ///
+ GreedOnly = 1,
+
+ ///
+ /// The lootmaster rule.
+ ///
+ Lootmaster = 2,
+ }
+
+ ///
+ /// Category flags for the class.
+ ///
+ public enum Category
+ {
+ ///
+ /// The duty category.
+ ///
+ Duty = 0,
+
+ ///
+ /// The quest battle category.
+ ///
+ QuestBattles = 1 << 0,
+
+ ///
+ /// The fate category.
+ ///
+ Fates = 1 << 1,
+
+ ///
+ /// The treasure hunt category.
+ ///
+ TreasureHunt = 1 << 2,
+
+ ///
+ /// The hunt category.
+ ///
+ TheHunt = 1 << 3,
+
+ ///
+ /// The gathering forays category.
+ ///
+ GatheringForays = 1 << 4,
+
+ ///
+ /// The deep dungeons category.
+ ///
+ DeepDungeons = 1 << 5,
+
+ ///
+ /// The adventuring forays category.
+ ///
+ AdventuringForays = 1 << 6,
+ }
+
+ ///
+ /// Duty type flags for the class.
+ ///
+ public enum DutyType
+ {
+ ///
+ /// No duty type.
+ ///
+ Other = 0,
+
+ ///
+ /// The roulette duty type.
+ ///
+ Roulette = 1 << 0,
+
+ ///
+ /// The normal duty type.
+ ///
+ Normal = 1 << 1,
+ }
+
+ ///
+ /// Extensions for the enum.
+ ///
+ public static class JobFlagsExtensions
+ {
+ ///
+ /// Get the actual ClassJob from the in-game sheets for this JobFlags.
+ ///
+ /// A JobFlags enum member.
+ /// A DataManager to get the ClassJob from.
+ /// A ClassJob if found or null if not.
+ public static ClassJob ClassJob(this JobFlags job, DataManager data)
+ {
+ var jobs = data.GetExcelSheet();
+
+ uint? row = job switch
+ {
+ JobFlags.Gladiator => 1,
+ JobFlags.Pugilist => 2,
+ JobFlags.Marauder => 3,
+ JobFlags.Lancer => 4,
+ JobFlags.Archer => 5,
+ JobFlags.Conjurer => 6,
+ JobFlags.Thaumaturge => 7,
+ JobFlags.Paladin => 19,
+ JobFlags.Monk => 20,
+ JobFlags.Warrior => 21,
+ JobFlags.Dragoon => 22,
+ JobFlags.Bard => 23,
+ JobFlags.WhiteMage => 24,
+ JobFlags.BlackMage => 25,
+ JobFlags.Arcanist => 26,
+ JobFlags.Summoner => 27,
+ JobFlags.Scholar => 28,
+ JobFlags.Rogue => 29,
+ JobFlags.Ninja => 30,
+ JobFlags.Machinist => 31,
+ JobFlags.DarkKnight => 32,
+ JobFlags.Astrologian => 33,
+ JobFlags.Samurai => 34,
+ JobFlags.RedMage => 35,
+ JobFlags.BlueMage => 36,
+ JobFlags.Gunbreaker => 37,
+ JobFlags.Dancer => 38,
+ _ => null,
+ };
+
+ return row == null ? null : jobs.GetRow((uint)row);
+ }
+ }
+}
diff --git a/Dalamud/Game/Internal/Gui/TargetManager.cs b/Dalamud/Game/Internal/Gui/TargetManager.cs
deleted file mode 100644
index 202684581..000000000
--- a/Dalamud/Game/Internal/Gui/TargetManager.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using Dalamud.Game.ClientState;
-using Dalamud.Game.ClientState.Actors.Types;
-using Dalamud.Game.ClientState.Structs.JobGauge;
-using Dalamud.Hooking;
-using Serilog;
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Dalamud.Game.Internal.Gui {
- public class TargetManager {
- public delegate IntPtr GetTargetDelegate(IntPtr manager);
-
- private Hook getTargetHook;
-
- private TargetManagerAddressResolver Address;
-
- public unsafe TargetManager(Dalamud dalamud, SigScanner scanner) {
- this.Address = new TargetManagerAddressResolver();
- this.Address.Setup(scanner);
-
- Log.Verbose("===== T A R G E T M A N A G E R =====");
- Log.Verbose("GetTarget address {GetTarget}", Address.GetTarget);
-
- this.getTargetHook = new Hook(this.Address.GetTarget, new GetTargetDelegate(GetTargetDetour), this);
- }
-
- public void Enable() {
- this.getTargetHook.Enable();
- }
-
- public void Dispose() {
- this.getTargetHook.Dispose();
- }
-
- private IntPtr GetTargetDetour(IntPtr manager)
- {
- try {
- var res = this.getTargetHook.Original(manager);
-
- var test = Marshal.ReadInt32(res);
-
- Log.Debug($"GetTargetDetour {manager.ToInt64():X} -> RET: {res:X}");
-
- return res;
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Exception GetTargetDetour hook.");
- return this.getTargetHook.Original(manager);
- }
- }
- }
-}
diff --git a/Dalamud/Game/Internal/Gui/TargetManagerAddressResolver.cs b/Dalamud/Game/Internal/Gui/TargetManagerAddressResolver.cs
deleted file mode 100644
index 137742bc0..000000000
--- a/Dalamud/Game/Internal/Gui/TargetManagerAddressResolver.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Dalamud.Game.Internal.Gui {
- class TargetManagerAddressResolver : BaseAddressResolver {
- public IntPtr GetTarget { get; private set; }
-
- protected override void Setup64Bit(SigScanner sig) {
- this.GetTarget = sig.ScanText("40 57 48 83 EC 40 48 8B F9 48 8B 49 08 48 8B 01 FF 50 40 66 83 B8 CA 81 00 00 00 74 33 48 8B 4F 08 48 8B 01 FF 50 40 66 83 B8 CA 81 00 00 04 74");
- }
- }
-}
diff --git a/Dalamud/Game/Internal/Gui/Toast/QuestToastOptions.cs b/Dalamud/Game/Internal/Gui/Toast/QuestToastOptions.cs
index 7e7f46484..34fa674e7 100755
--- a/Dalamud/Game/Internal/Gui/Toast/QuestToastOptions.cs
+++ b/Dalamud/Game/Internal/Gui/Toast/QuestToastOptions.cs
@@ -1,5 +1,8 @@
-namespace Dalamud.Game.Internal.Gui.Toast
+namespace Dalamud.Game.Internal.Gui.Toast
{
+ ///
+ /// This class represents options that can be used with the class for the quest toast variant.
+ ///
public sealed class QuestToastOptions
{
///
@@ -25,12 +28,5 @@
/// This only works if is non-zero or is true.
///
public bool PlaySound { get; set; } = false;
-
- internal (uint, uint) DetermineParameterOrder()
- {
- return this.DisplayCheckmark
- ? (ToastGui.QuestToastCheckmarkMagic, this.IconId)
- : (this.IconId, 0);
- }
}
}
diff --git a/Dalamud/Game/Internal/Gui/Toast/QuestToastPosition.cs b/Dalamud/Game/Internal/Gui/Toast/QuestToastPosition.cs
index 071a719b3..a6ea499b1 100755
--- a/Dalamud/Game/Internal/Gui/Toast/QuestToastPosition.cs
+++ b/Dalamud/Game/Internal/Gui/Toast/QuestToastPosition.cs
@@ -1,9 +1,23 @@
-namespace Dalamud.Game.Internal.Gui.Toast
+namespace Dalamud.Game.Internal.Gui.Toast
{
+ ///
+ /// The alignment of native quest toast windows.
+ ///
public enum QuestToastPosition
{
+ ///
+ /// The toast will be aligned screen centre.
+ ///
Centre = 0,
+
+ ///
+ /// The toast will be aligned screen right.
+ ///
Right = 1,
+
+ ///
+ /// The toast will be aligned screen left.
+ ///
Left = 2,
}
}
diff --git a/Dalamud/Game/Internal/Gui/Toast/ToastOptions.cs b/Dalamud/Game/Internal/Gui/Toast/ToastOptions.cs
index d2f010db1..5be757393 100755
--- a/Dalamud/Game/Internal/Gui/Toast/ToastOptions.cs
+++ b/Dalamud/Game/Internal/Gui/Toast/ToastOptions.cs
@@ -1,5 +1,8 @@
namespace Dalamud.Game.Internal.Gui.Toast
{
+ ///
+ /// This class represents options that can be used with the class.
+ ///
public sealed class ToastOptions
{
///
diff --git a/Dalamud/Game/Internal/Gui/Toast/ToastPosition.cs b/Dalamud/Game/Internal/Gui/Toast/ToastPosition.cs
index 1a0aecda5..4c01cb709 100755
--- a/Dalamud/Game/Internal/Gui/Toast/ToastPosition.cs
+++ b/Dalamud/Game/Internal/Gui/Toast/ToastPosition.cs
@@ -1,8 +1,18 @@
-namespace Dalamud.Game.Internal.Gui.Toast
+namespace Dalamud.Game.Internal.Gui.Toast
{
+ ///
+ /// The positioning of native toast windows.
+ ///
public enum ToastPosition : byte
{
+ ///
+ /// The toast will be towards the bottom.
+ ///
Bottom = 0,
+
+ ///
+ /// The toast will be towards the top.
+ ///
Top = 1,
}
}
diff --git a/Dalamud/Game/Internal/Gui/Toast/ToastSpeed.cs b/Dalamud/Game/Internal/Gui/Toast/ToastSpeed.cs
index 0c3a4c104..620c65301 100755
--- a/Dalamud/Game/Internal/Gui/Toast/ToastSpeed.cs
+++ b/Dalamud/Game/Internal/Gui/Toast/ToastSpeed.cs
@@ -1,5 +1,8 @@
-namespace Dalamud.Game.Internal.Gui.Toast
+namespace Dalamud.Game.Internal.Gui.Toast
{
+ ///
+ /// The speed at which native toast windows will persist.
+ ///
public enum ToastSpeed : byte
{
///
diff --git a/Dalamud/Game/Internal/Gui/ToastGui.cs b/Dalamud/Game/Internal/Gui/ToastGui.cs
index e4e4b305c..bb4883e7a 100755
--- a/Dalamud/Game/Internal/Gui/ToastGui.cs
+++ b/Dalamud/Game/Internal/Gui/ToastGui.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Text;
@@ -8,18 +8,80 @@ using Dalamud.Hooking;
namespace Dalamud.Game.Internal.Gui
{
- public sealed class ToastGui : IDisposable
+ ///
+ /// This class facilitates interacting with and creating native toast windows.
+ ///
+ public sealed partial class ToastGui : IDisposable
{
- internal const uint QuestToastCheckmarkMagic = 60081;
+ private const uint QuestToastCheckmarkMagic = 60081;
- #region Events
+ private readonly Dalamud dalamud;
+ private readonly ToastGuiAddressResolver address;
+ private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new();
+ private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new();
+ private readonly Queue errorQueue = new();
+
+ private readonly Hook showNormalToastHook;
+ private readonly Hook showQuestToastHook;
+ private readonly Hook showErrorToastHook;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SigScanner instance.
+ /// The Dalamud instance.
+ public ToastGui(SigScanner scanner, Dalamud dalamud)
+ {
+ this.dalamud = dalamud;
+
+ this.address = new ToastGuiAddressResolver();
+ this.address.Setup(scanner);
+
+ this.showNormalToastHook = new Hook(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
+ this.showQuestToastHook = new Hook(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
+ this.showErrorToastHook = new Hook(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
+ }
+
+ #region Event delegates
+
+ ///
+ /// A delegate type used when a normal toast window appears.
+ ///
+ /// The message displayed.
+ /// Assorted toast options.
+ /// Whether the toast has been handled or should be propagated.
public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled);
+ ///
+ /// A delegate type used when a quest toast window appears.
+ ///
+ /// The message displayed.
+ /// Assorted toast options.
+ /// Whether the toast has been handled or should be propagated.
public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled);
+ ///
+ /// A delegate type used when an error toast window appears.
+ ///
+ /// The message displayed.
+ /// Whether the toast has been handled or should be propagated.
public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled);
+ #endregion
+
+ #region Marshal delegates
+
+ private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
+
+ private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
+
+ private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
+
+ #endregion
+
+ #region Events
+
///
/// Event that will be fired when a toast is sent by the game or a plugin.
///
@@ -37,48 +99,9 @@ namespace Dalamud.Game.Internal.Gui
#endregion
- #region Hooks
-
- private readonly Hook showNormalToastHook;
-
- private readonly Hook showQuestToastHook;
-
- private readonly Hook showErrorToastHook;
-
- #endregion
-
- #region Delegates
-
- private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
-
- private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
-
- private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
-
- #endregion
-
- private Dalamud Dalamud { get; }
-
- private ToastGuiAddressResolver Address { get; }
-
- private Queue<(byte[], ToastOptions)> NormalQueue { get; } = new Queue<(byte[], ToastOptions)>();
-
- private Queue<(byte[], QuestToastOptions)> QuestQueue { get; } = new Queue<(byte[], QuestToastOptions)>();
-
- private Queue ErrorQueue { get; } = new Queue();
-
- public ToastGui(SigScanner scanner, Dalamud dalamud)
- {
- this.Dalamud = dalamud;
-
- this.Address = new ToastGuiAddressResolver();
- this.Address.Setup(scanner);
-
- this.showNormalToastHook = new Hook(this.Address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
- this.showQuestToastHook = new Hook(this.Address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
- this.showErrorToastHook = new Hook(this.Address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
- }
-
+ ///
+ /// Enables this module.
+ ///
public void Enable()
{
this.showNormalToastHook.Enable();
@@ -86,6 +109,9 @@ namespace Dalamud.Game.Internal.Gui
this.showErrorToastHook.Enable();
}
+ ///
+ /// Disposes of managed and unmanaged resources.
+ ///
public void Dispose()
{
this.showNormalToastHook.Dispose();
@@ -93,6 +119,30 @@ namespace Dalamud.Game.Internal.Gui
this.showErrorToastHook.Dispose();
}
+ ///
+ /// Process the toast queue.
+ ///
+ internal void UpdateQueue()
+ {
+ while (this.normalQueue.Count > 0)
+ {
+ var (message, options) = this.normalQueue.Dequeue();
+ this.ShowNormal(message, options);
+ }
+
+ while (this.questQueue.Count > 0)
+ {
+ var (message, options) = this.questQueue.Dequeue();
+ this.ShowQuest(message, options);
+ }
+
+ while (this.errorQueue.Count > 0)
+ {
+ var message = this.errorQueue.Dequeue();
+ this.ShowError(message);
+ }
+ }
+
private static byte[] Terminate(byte[] source)
{
var terminated = new byte[source.Length + 1];
@@ -107,7 +157,7 @@ namespace Dalamud.Game.Internal.Gui
var bytes = new List();
unsafe
{
- var ptr = (byte*) text;
+ var ptr = (byte*)text;
while (*ptr != 0)
{
bytes.Add(*ptr);
@@ -116,62 +166,42 @@ namespace Dalamud.Game.Internal.Gui
}
// call events
- return this.Dalamud.SeStringManager.Parse(bytes.ToArray());
+ return this.dalamud.SeStringManager.Parse(bytes.ToArray());
}
+ }
- ///
- /// Process the toast queue.
- ///
- internal void UpdateQueue()
- {
- while (this.NormalQueue.Count > 0)
- {
- var (message, options) = this.NormalQueue.Dequeue();
- this.ShowNormal(message, options);
- }
-
- while (this.QuestQueue.Count > 0)
- {
- var (message, options) = this.QuestQueue.Dequeue();
- this.ShowQuest(message, options);
- }
-
- while (this.ErrorQueue.Count > 0)
- {
- var message = this.ErrorQueue.Dequeue();
- this.ShowError(message);
- }
- }
-
- #region Normal API
-
+ ///
+ /// Handles normal toasts.
+ ///
+ public sealed partial class ToastGui
+ {
///
/// Show a toast message with the given content.
///
- /// The message to be shown
- /// Options for the toast
+ /// The message to be shown.
+ /// Options for the toast.
public void ShowNormal(string message, ToastOptions options = null)
{
options ??= new ToastOptions();
- this.NormalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
+ this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
}
///
/// Show a toast message with the given content.
///
- /// The message to be shown
- /// Options for the toast
+ /// The message to be shown.
+ /// Options for the toast.
public void ShowNormal(SeString message, ToastOptions options = null)
{
options ??= new ToastOptions();
- this.NormalQueue.Enqueue((message.Encode(), options));
+ this.normalQueue.Enqueue((message.Encode(), options));
}
private void ShowNormal(byte[] bytes, ToastOptions options = null)
{
options ??= new ToastOptions();
- var manager = this.Dalamud.Framework.Gui.GetUIModule();
+ var manager = this.dalamud.Framework.Gui.GetUIModule();
// terminate the string
var terminated = Terminate(bytes);
@@ -180,104 +210,11 @@ namespace Dalamud.Game.Internal.Gui
{
fixed (byte* ptr = terminated)
{
- this.HandleNormalToastDetour(manager, (IntPtr) ptr, 5, (byte) options.Position, (byte) options.Speed, 0);
+ this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
}
}
}
- #endregion
-
- #region Quest API
-
- ///
- /// Show a quest toast message with the given content.
- ///
- /// The message to be shown
- /// Options for the toast
- public void ShowQuest(string message, QuestToastOptions options = null)
- {
- options ??= new QuestToastOptions();
- this.QuestQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
- }
-
- ///
- /// Show a quest toast message with the given content.
- ///
- /// The message to be shown
- /// Options for the toast
- public void ShowQuest(SeString message, QuestToastOptions options = null)
- {
- options ??= new QuestToastOptions();
- this.QuestQueue.Enqueue((message.Encode(), options));
- }
-
- private void ShowQuest(byte[] bytes, QuestToastOptions options = null)
- {
- options ??= new QuestToastOptions();
-
- var manager = this.Dalamud.Framework.Gui.GetUIModule();
-
- // terminate the string
- var terminated = Terminate(bytes);
-
- var (ioc1, ioc2) = options.DetermineParameterOrder();
-
- unsafe
- {
- fixed (byte* ptr = terminated)
- {
- this.HandleQuestToastDetour(
- manager,
- (int) options.Position,
- (IntPtr) ptr,
- ioc1,
- options.PlaySound ? (byte) 1 : (byte) 0,
- ioc2,
- 0);
- }
- }
- }
-
- #endregion
-
- #region Error API
-
- ///
- /// Show an error toast message with the given content.
- ///
- /// The message to be shown
- public void ShowError(string message)
- {
- this.ErrorQueue.Enqueue(Encoding.UTF8.GetBytes(message));
- }
-
- ///
- /// Show an error toast message with the given content.
- ///
- /// The message to be shown
- public void ShowError(SeString message)
- {
- this.ErrorQueue.Enqueue(message.Encode());
- }
-
- private void ShowError(byte[] bytes)
- {
- var manager = this.Dalamud.Framework.Gui.GetUIModule();
-
- // terminate the string
- var terminated = Terminate(bytes);
-
- unsafe
- {
- fixed (byte* ptr = terminated)
- {
- this.HandleErrorToastDetour(manager, (IntPtr) ptr, 0);
- }
- }
- }
-
- #endregion
-
private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId)
{
if (text == IntPtr.Zero)
@@ -290,8 +227,8 @@ namespace Dalamud.Game.Internal.Gui
var str = this.ParseString(text);
var options = new ToastOptions
{
- Position = (ToastPosition) isTop,
- Speed = (ToastSpeed) isFast,
+ Position = (ToastPosition)isTop,
+ Speed = (ToastSpeed)isFast,
};
this.OnToast?.Invoke(ref str, ref options, ref isHandled);
@@ -308,7 +245,62 @@ namespace Dalamud.Game.Internal.Gui
{
fixed (byte* message = terminated)
{
- return this.showNormalToastHook.Original(manager, (IntPtr) message, layer, (byte) options.Position, (byte) options.Speed, logMessageId);
+ return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Handles quest toasts.
+ ///
+ public sealed partial class ToastGui
+ {
+ ///
+ /// Show a quest toast message with the given content.
+ ///
+ /// The message to be shown.
+ /// Options for the toast.
+ public void ShowQuest(string message, QuestToastOptions options = null)
+ {
+ options ??= new QuestToastOptions();
+ this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
+ }
+
+ ///
+ /// Show a quest toast message with the given content.
+ ///
+ /// The message to be shown.
+ /// Options for the toast.
+ public void ShowQuest(SeString message, QuestToastOptions options = null)
+ {
+ options ??= new QuestToastOptions();
+ this.questQueue.Enqueue((message.Encode(), options));
+ }
+
+ private void ShowQuest(byte[] bytes, QuestToastOptions options = null)
+ {
+ options ??= new QuestToastOptions();
+
+ var manager = this.dalamud.Framework.Gui.GetUIModule();
+
+ // terminate the string
+ var terminated = Terminate(bytes);
+
+ var (ioc1, ioc2) = this.DetermineParameterOrder(options);
+
+ unsafe
+ {
+ fixed (byte* ptr = terminated)
+ {
+ this.HandleQuestToastDetour(
+ manager,
+ (int)options.Position,
+ (IntPtr)ptr,
+ ioc1,
+ options.PlaySound ? (byte)1 : (byte)0,
+ ioc2,
+ 0);
}
}
}
@@ -325,7 +317,7 @@ namespace Dalamud.Game.Internal.Gui
var str = this.ParseString(text);
var options = new QuestToastOptions
{
- Position = (QuestToastPosition) position,
+ Position = (QuestToastPosition)position,
DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic,
IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1,
PlaySound = playSound == 1,
@@ -341,7 +333,7 @@ namespace Dalamud.Game.Internal.Gui
var terminated = Terminate(str.Encode());
- var (ioc1, ioc2) = options.DetermineParameterOrder();
+ var (ioc1, ioc2) = this.DetermineParameterOrder(options);
unsafe
{
@@ -349,16 +341,63 @@ namespace Dalamud.Game.Internal.Gui
{
return this.showQuestToastHook.Original(
manager,
- (int) options.Position,
- (IntPtr) message,
+ (int)options.Position,
+ (IntPtr)message,
ioc1,
- options.PlaySound ? (byte) 1 : (byte) 0,
+ options.PlaySound ? (byte)1 : (byte)0,
ioc2,
0);
}
}
}
+ private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options)
+ {
+ return options.DisplayCheckmark
+ ? (QuestToastCheckmarkMagic, options.IconId)
+ : (options.IconId, 0);
+ }
+ }
+
+ ///
+ /// Handles error toasts.
+ ///
+ public sealed partial class ToastGui
+ {
+ ///
+ /// Show an error toast message with the given content.
+ ///
+ /// The message to be shown.
+ public void ShowError(string message)
+ {
+ this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message));
+ }
+
+ ///
+ /// Show an error toast message with the given content.
+ ///
+ /// The message to be shown.
+ public void ShowError(SeString message)
+ {
+ this.errorQueue.Enqueue(message.Encode());
+ }
+
+ private void ShowError(byte[] bytes)
+ {
+ var manager = this.dalamud.Framework.Gui.GetUIModule();
+
+ // terminate the string
+ var terminated = Terminate(bytes);
+
+ unsafe
+ {
+ fixed (byte* ptr = terminated)
+ {
+ this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0);
+ }
+ }
+ }
+
private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe)
{
if (text == IntPtr.Zero)
@@ -384,7 +423,7 @@ namespace Dalamud.Game.Internal.Gui
{
fixed (byte* message = terminated)
{
- return this.showErrorToastHook.Original(manager, (IntPtr) message, respectsHidingMaybe);
+ return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe);
}
}
}
diff --git a/Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs b/Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs
index c89eeb20b..93b8eba04 100755
--- a/Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs
+++ b/Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs
@@ -1,15 +1,28 @@
-using System;
+using System;
namespace Dalamud.Game.Internal.Gui
{
+ ///
+ /// An address resolver for the class.
+ ///
public class ToastGuiAddressResolver : BaseAddressResolver
{
+ ///
+ /// Gets the address of the native ShowNormalToast method.
+ ///
public IntPtr ShowNormalToast { get; private set; }
+ ///
+ /// Gets the address of the native ShowQuestToast method.
+ ///
public IntPtr ShowQuestToast { get; private set; }
+ ///
+ /// Gets the address of the ShowErrorToast method.
+ ///
public IntPtr ShowErrorToast { get; private set; }
+ ///
protected override void Setup64Bit(SigScanner sig)
{
this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??");
diff --git a/Dalamud/Game/Internal/Libc/LibcFunction.cs b/Dalamud/Game/Internal/Libc/LibcFunction.cs
index 598aeb286..33990caae 100644
--- a/Dalamud/Game/Internal/Libc/LibcFunction.cs
+++ b/Dalamud/Game/Internal/Libc/LibcFunction.cs
@@ -1,45 +1,65 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
-using Serilog;
-namespace Dalamud.Game.Internal.Libc {
- public sealed class LibcFunction {
+namespace Dalamud.Game.Internal.Libc
+{
+ ///
+ /// This class handles creating cstrings utilizing native game methods.
+ ///
+ public sealed class LibcFunction
+ {
+ private readonly LibcFunctionAddressResolver address;
+ private readonly StdStringFromCStringDelegate stdStringCtorCString;
+ private readonly StdStringDeallocateDelegate stdStringDeallocate;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SigScanner instance.
+ public LibcFunction(SigScanner scanner)
+ {
+ this.address = new LibcFunctionAddressResolver();
+ this.address.Setup(scanner);
+
+ this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer(this.address.StdStringFromCstring);
+ this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(this.address.StdStringDeallocate);
+ }
+
// TODO: prolly callconv is not okay in x86
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)]byte[] content, IntPtr size);
+ private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)] byte[] content, IntPtr size);
// TODO: prolly callconv is not okay in x86
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr StdStringDeallocateDelegate(IntPtr address);
-
- private LibcFunctionAddressResolver Address { get; }
- private readonly StdStringFromCStringDelegate stdStringCtorCString;
- private readonly StdStringDeallocateDelegate stdStringDeallocate;
-
- public LibcFunction(SigScanner scanner) {
- Address = new LibcFunctionAddressResolver();
- Address.Setup(scanner);
-
- this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer(Address.StdStringFromCstring);
- this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(Address.StdStringDeallocate);
- }
-
- public OwnedStdString NewString(byte[] content) {
+ ///
+ /// Create a new string from the given bytes.
+ ///
+ /// The bytes to convert.
+ /// An owned std string object.
+ public OwnedStdString NewString(byte[] content)
+ {
// While 0x70 bytes in the memory should be enough in DX11 version,
// I don't trust my analysis so we're just going to allocate almost two times more than that.
var pString = Marshal.AllocHGlobal(256);
-
+
// Initialize a string
var size = new IntPtr(content.Length);
var pReallocString = this.stdStringCtorCString(pString, content, size);
-
- //Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString);
-
- return new OwnedStdString(pReallocString, DeallocateStdString);
+
+ // Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString);
+
+ return new OwnedStdString(pReallocString, this.DeallocateStdString);
}
+ ///
+ /// Create a new string form the given bytes.
+ ///
+ /// The bytes to convert.
+ /// A non-default encoding.
+ /// An owned std string object.
public OwnedStdString NewString(string content, Encoding encoding = null)
{
encoding ??= Encoding.UTF8;
@@ -47,7 +67,8 @@ namespace Dalamud.Game.Internal.Libc {
return this.NewString(encoding.GetBytes(content));
}
- private void DeallocateStdString(IntPtr address) {
+ private void DeallocateStdString(IntPtr address)
+ {
this.stdStringDeallocate(address);
}
}
diff --git a/Dalamud/Game/Internal/Libc/LibcFunctionAddressResolver.cs b/Dalamud/Game/Internal/Libc/LibcFunctionAddressResolver.cs
index aeaefa595..b96a37493 100644
--- a/Dalamud/Game/Internal/Libc/LibcFunctionAddressResolver.cs
+++ b/Dalamud/Game/Internal/Libc/LibcFunctionAddressResolver.cs
@@ -1,16 +1,29 @@
-using System;
-using System.Security.Policy;
+using System;
-namespace Dalamud.Game.Internal.Libc {
- public sealed class LibcFunctionAddressResolver : BaseAddressResolver {
+namespace Dalamud.Game.Internal.Libc
+{
+ ///
+ /// The address resolver for the class.
+ ///
+ public sealed class LibcFunctionAddressResolver : BaseAddressResolver
+ {
private delegate IntPtr StringFromCString();
+ ///
+ /// Gets the address of the native StdStringFromCstring method.
+ ///
public IntPtr StdStringFromCstring { get; private set; }
+
+ ///
+ /// Gets the address of the native StdStringDeallocate method.
+ ///
public IntPtr StdStringDeallocate { get; private set; }
-
- protected override void Setup64Bit(SigScanner sig) {
- StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
- StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
+
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
+ this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
}
}
}
diff --git a/Dalamud/Game/Internal/Libc/OwnedStdString.cs b/Dalamud/Game/Internal/Libc/OwnedStdString.cs
index ea68d8f91..7969e4947 100644
--- a/Dalamud/Game/Internal/Libc/OwnedStdString.cs
+++ b/Dalamud/Game/Internal/Libc/OwnedStdString.cs
@@ -1,20 +1,18 @@
using System;
using System.Runtime.InteropServices;
-using Serilog;
-namespace Dalamud.Game.Internal.Libc {
- public sealed class OwnedStdString : IDisposable {
- internal delegate void DeallocatorDelegate(IntPtr address);
-
- // ala. the drop flag
- private bool isDisposed;
-
+namespace Dalamud.Game.Internal.Libc
+{
+ ///
+ /// An address wrapper around the class.
+ ///
+ public sealed partial class OwnedStdString
+ {
private readonly DeallocatorDelegate dealloc;
-
- public IntPtr Address { get; private set; }
-
+
///
- /// Construct a wrapper around std::string
+ /// Initializes a new instance of the class.
+ /// Construct a wrapper around std::string.
///
///
/// Violating any of these might cause an undefined hehaviour.
@@ -22,47 +20,82 @@ namespace Dalamud.Game.Internal.Libc {
/// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address.
/// 3. std::string object pointed by address must be initialized before calling this function.
///
- ///
+ /// The address of the owned std string.
/// A deallocator function.
- ///
- internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) {
- Address = address;
+ internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc)
+ {
+ this.Address = address;
this.dealloc = dealloc;
}
-
- ~OwnedStdString() {
- ReleaseUnmanagedResources();
+
+ ///
+ /// The delegate type that deallocates a std string.
+ ///
+ /// Address to deallocate.
+ internal delegate void DeallocatorDelegate(IntPtr address);
+
+ ///
+ /// Gets the address of the std string.
+ ///
+ public IntPtr Address { get; private set; }
+
+ ///
+ /// Read the wrapped StdString.
+ ///
+ /// The StdString.
+ public StdString Read() => StdString.ReadFromPointer(this.Address);
+ }
+
+ ///
+ /// Implements IDisposable.
+ ///
+ public sealed partial class OwnedStdString : IDisposable
+ {
+ private bool isDisposed;
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~OwnedStdString() => this.Dispose(false);
+
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ this.Dispose(true);
}
- private void ReleaseUnmanagedResources() {
- if (Address == IntPtr.Zero) {
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ /// A value indicating whether this was called via Dispose or finalized.
+ public void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ return;
+
+ this.isDisposed = true;
+
+ if (disposing)
+ {
+ }
+
+ if (this.Address == IntPtr.Zero)
+ {
// Something got seriously fucked.
throw new AccessViolationException();
}
-
+
// Deallocate inner string first
- this.dealloc(Address);
-
+ this.dealloc(this.Address);
+
// Free the heap
- Marshal.FreeHGlobal(Address);
-
+ Marshal.FreeHGlobal(this.Address);
+
// Better safe (running on a nullptr) than sorry. (running on a dangling pointer)
- Address = IntPtr.Zero;
- }
-
- public void Dispose() {
- // No double free plz, kthx.
- if (this.isDisposed) {
- return;
- }
- this.isDisposed = true;
-
- ReleaseUnmanagedResources();
- GC.SuppressFinalize(this);
- }
-
- public StdString Read() {
- return StdString.ReadFromPointer(Address);
+ this.Address = IntPtr.Zero;
}
}
}
diff --git a/Dalamud/Game/Internal/Libc/StdString.cs b/Dalamud/Game/Internal/Libc/StdString.cs
index c91b26690..9b627c88d 100644
--- a/Dalamud/Game/Internal/Libc/StdString.cs
+++ b/Dalamud/Game/Internal/Libc/StdString.cs
@@ -1,31 +1,56 @@
using System;
-using System.Linq;
using System.Runtime.InteropServices;
-using System.Security.Cryptography.X509Certificates;
using System.Text;
-using Newtonsoft.Json.Linq;
-using Serilog;
-namespace Dalamud.Game.Internal.Libc {
+namespace Dalamud.Game.Internal.Libc
+{
///
- /// Interation with std::string
+ /// Interation with std::string.
///
- public class StdString {
- public static StdString ReadFromPointer(IntPtr cstring) {
- unsafe {
- if (cstring == IntPtr.Zero) {
+ public class StdString
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ private StdString()
+ {
+ }
+
+ ///
+ /// Gets the value of the cstring.
+ ///
+ public string Value { get; private set; }
+
+ ///
+ /// Gets or sets the raw byte representation of the cstring.
+ ///
+ public byte[] RawData { get; set; }
+
+ ///
+ /// Marshal a null terminated cstring from memory to a UTF-8 encoded string.
+ ///
+ /// Address of the cstring.
+ /// A UTF-8 encoded string.
+ public static StdString ReadFromPointer(IntPtr cstring)
+ {
+ unsafe
+ {
+ if (cstring == IntPtr.Zero)
+ {
throw new ArgumentNullException(nameof(cstring));
}
-
+
var innerAddress = Marshal.ReadIntPtr(cstring);
- if (innerAddress == IntPtr.Zero) {
+ if (innerAddress == IntPtr.Zero)
+ {
throw new NullReferenceException("Inner reference to the cstring is null.");
}
- var count = 0;
-
// Count the number of chars. String is assumed to be zero-terminated.
- while (Marshal.ReadByte(innerAddress + count) != 0) {
+
+ var count = 0;
+ while (Marshal.ReadByte(innerAddress, count) != 0)
+ {
count += 1;
}
@@ -33,17 +58,12 @@ namespace Dalamud.Game.Internal.Libc {
var rawData = new byte[count];
Marshal.Copy(innerAddress, rawData, 0, count);
- return new StdString {
+ return new StdString
+ {
RawData = rawData,
- Value = Encoding.UTF8.GetString(rawData)
+ Value = Encoding.UTF8.GetString(rawData),
};
}
}
-
- private StdString() { }
-
- public string Value { get; private set; }
-
- public byte[] RawData { get; set; }
}
}
diff --git a/Dalamud/Game/Internal/Network/GameNetwork.cs b/Dalamud/Game/Internal/Network/GameNetwork.cs
index d3e25675c..1ecc09352 100644
--- a/Dalamud/Game/Internal/Network/GameNetwork.cs
+++ b/Dalamud/Game/Internal/Network/GameNetwork.cs
@@ -1,88 +1,128 @@
using System;
using System.Collections.Generic;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+
using Dalamud.Hooking;
using Serilog;
-using SharpDX.DXGI;
-
-namespace Dalamud.Game.Internal.Network {
- public sealed class GameNetwork : IDisposable {
- #region Hooks
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
- private readonly Hook processZonePacketDownHook;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
- private readonly Hook processZonePacketUpHook;
-
- #endregion
-
- private GameNetworkAddressResolver Address { get; }
- private IntPtr baseAddress;
-
- public delegate void OnNetworkMessageDelegate(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction);
+namespace Dalamud.Game.Internal.Network
+{
+ ///
+ /// This class handles interacting with game network events.
+ ///
+ public sealed class GameNetwork : IDisposable
+ {
///
/// Event that is called when a network message is sent/received.
///
public OnNetworkMessageDelegate OnNetworkMessage;
+ private readonly GameNetworkAddressResolver address;
+ private readonly Hook processZonePacketDownHook;
+ private readonly Hook processZonePacketUpHook;
+ private readonly Queue zoneInjectQueue = new();
+ private IntPtr baseAddress;
- private readonly Queue zoneInjectQueue = new Queue();
-
- public GameNetwork(SigScanner scanner) {
- Address = new GameNetworkAddressResolver();
- Address.Setup(scanner);
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SigScanner instance.
+ public GameNetwork(SigScanner scanner)
+ {
+ this.address = new GameNetworkAddressResolver();
+ this.address.Setup(scanner);
Log.Verbose("===== G A M E N E T W O R K =====");
- Log.Verbose("ProcessZonePacketDown address {ProcessZonePacketDown}", Address.ProcessZonePacketDown);
- Log.Verbose("ProcessZonePacketUp address {ProcessZonePacketUp}", Address.ProcessZonePacketUp);
+ Log.Verbose("ProcessZonePacketDown address {ProcessZonePacketDown}", this.address.ProcessZonePacketDown);
+ Log.Verbose("ProcessZonePacketUp address {ProcessZonePacketUp}", this.address.ProcessZonePacketUp);
- this.processZonePacketDownHook =
- new Hook(Address.ProcessZonePacketDown,
- new ProcessZonePacketDownDelegate(ProcessZonePacketDownDetour),
- this);
+ this.processZonePacketDownHook = new Hook(this.address.ProcessZonePacketDown, new ProcessZonePacketDownDelegate(this.ProcessZonePacketDownDetour), this);
- this.processZonePacketUpHook =
- new Hook(Address.ProcessZonePacketUp,
- new ProcessZonePacketUpDelegate(ProcessZonePacketUpDetour),
- this);
+ this.processZonePacketUpHook = new Hook(this.address.ProcessZonePacketUp, new ProcessZonePacketUpDelegate(this.ProcessZonePacketUpDetour), this);
}
- public void Enable() {
+ ///
+ /// The delegate type of a network message event.
+ ///
+ /// The pointer to the raw data.
+ /// The operation ID code.
+ /// The source actor ID.
+ /// The taret actor ID.
+ /// The direction of the packed.
+ public delegate void OnNetworkMessageDelegate(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
+
+ ///
+ /// Enable this module.
+ ///
+ public void Enable()
+ {
this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
}
- public void Dispose() {
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ public void Dispose()
+ {
this.processZonePacketDownHook.Dispose();
this.processZonePacketUpHook.Dispose();
}
- private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) {
+ ///
+ /// Process a chat queue.
+ ///
+ /// The Framework instance.
+ public void UpdateQueue(Framework framework)
+ {
+ while (this.zoneInjectQueue.Count > 0)
+ {
+ var packetData = this.zoneInjectQueue.Dequeue();
+
+ var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length);
+ Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length);
+
+ if (this.baseAddress != IntPtr.Zero)
+ {
+ this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData);
+ }
+
+ Marshal.FreeHGlobal(unmanagedPacketData);
+ }
+ }
+
+ private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr)
+ {
this.baseAddress = a;
// Go back 0x10 to get back to the start of the packet header
dataPtr -= 0x10;
- try {
-
-
+ try
+ {
// Call events
- this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort) Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
+ this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10);
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
string header;
- try {
+ try
+ {
var data = new byte[32];
Marshal.Copy(dataPtr, data, 0, 32);
header = BitConverter.ToString(data);
- } catch (Exception) {
+ }
+ catch (Exception)
+ {
header = "failed";
}
@@ -92,13 +132,13 @@ namespace Dalamud.Game.Internal.Network {
}
}
- private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) {
-
+ private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4)
+ {
try
{
// Call events
// TODO: Implement actor IDs
- this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort) Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp);
+ this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp);
}
catch (Exception ex)
{
@@ -121,43 +161,26 @@ namespace Dalamud.Game.Internal.Network {
}
#if DEBUG
- public void InjectZoneProtoPacket(byte[] data) {
+ private void InjectZoneProtoPacket(byte[] data)
+ {
this.zoneInjectQueue.Enqueue(data);
}
- private void InjectActorControl(short cat, int param1) {
- var packetData = new byte[] {
+ private void InjectActorControl(short cat, int param1)
+ {
+ var packetData = new byte[]
+ {
0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00
+ 0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00,
};
- BitConverter.GetBytes((short) cat).CopyTo(packetData, 0x10);
+ BitConverter.GetBytes((short)cat).CopyTo(packetData, 0x10);
- BitConverter.GetBytes((UInt32) param1).CopyTo(packetData, 0x14);
+ BitConverter.GetBytes((uint)param1).CopyTo(packetData, 0x14);
- InjectZoneProtoPacket(packetData);
+ this.InjectZoneProtoPacket(packetData);
}
#endif
-
- ///
- /// Process a chat queue.
- ///
- public void UpdateQueue(Framework framework)
- {
- while (this.zoneInjectQueue.Count > 0)
- {
- var packetData = this.zoneInjectQueue.Dequeue();
-
- var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length);
- Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length);
-
- if (this.baseAddress != IntPtr.Zero) {
- this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData);
- }
-
- Marshal.FreeHGlobal(unmanagedPacketData);
- }
- }
}
}
diff --git a/Dalamud/Game/Internal/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Internal/Network/GameNetworkAddressResolver.cs
index dc2f165ee..9c5eb00fc 100644
--- a/Dalamud/Game/Internal/Network/GameNetworkAddressResolver.cs
+++ b/Dalamud/Game/Internal/Network/GameNetworkAddressResolver.cs
@@ -1,16 +1,29 @@
using System;
-namespace Dalamud.Game.Internal.Network {
- public sealed class GameNetworkAddressResolver : BaseAddressResolver {
+namespace Dalamud.Game.Internal.Network
+{
+ ///
+ /// The address resolver for the class.
+ ///
+ public sealed class GameNetworkAddressResolver : BaseAddressResolver
+ {
+ ///
+ /// Gets the address of the ProcessZonePacketDown method.
+ ///
public IntPtr ProcessZonePacketDown { get; private set; }
+
+ ///
+ /// Gets the address of the ProcessZonePacketUp method.
+ ///
public IntPtr ProcessZonePacketUp { get; private set; }
- protected override void Setup64Bit(SigScanner sig) {
- //ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
- //ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
- ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2");
- ProcessZonePacketUp =
- sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
+ // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
+ this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2");
+ this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
}
}
}
diff --git a/Dalamud/Game/Internal/Network/NetworkMessageDirection.cs b/Dalamud/Game/Internal/Network/NetworkMessageDirection.cs
index 0074a24de..22de9cb54 100644
--- a/Dalamud/Game/Internal/Network/NetworkMessageDirection.cs
+++ b/Dalamud/Game/Internal/Network/NetworkMessageDirection.cs
@@ -1,6 +1,18 @@
-namespace Dalamud.Game.Internal.Network {
- public enum NetworkMessageDirection {
+namespace Dalamud.Game.Internal.Network
+{
+ ///
+ /// This represents the direction of a network message.
+ ///
+ public enum NetworkMessageDirection
+ {
+ ///
+ /// A zone down message.
+ ///
ZoneDown,
- ZoneUp
+
+ ///
+ /// A zone up message.
+ ///
+ ZoneUp,
}
}
diff --git a/Dalamud/Game/Internal/Resource/ResourceManager.cs b/Dalamud/Game/Internal/Resource/ResourceManager.cs
index b12e1b8a6..0aa7db88c 100644
--- a/Dalamud/Game/Internal/Resource/ResourceManager.cs
+++ b/Dalamud/Game/Internal/Resource/ResourceManager.cs
@@ -1,135 +1,148 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
-using Dalamud.Game.Internal.Libc;
+
using Dalamud.Hooking;
using Serilog;
namespace Dalamud.Game.Internal.File
{
- public class ResourceManager {
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7);
+ ///
+ /// This class facilitates modifying how the game loads resources from disk.
+ ///
+ public class ResourceManager
+ {
+ private readonly Dalamud dalamud;
+ private readonly ResourceManagerAddressResolver address;
private readonly Hook getResourceAsyncHook;
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr GetResourceSyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6);
private readonly Hook getResourceSyncHook;
- private ResourceManagerAddressResolver Address { get; }
- private readonly Dalamud dalamud;
+ private Dictionary resourceHookMap = new();
- class ResourceHandleHookInfo {
- public string Path { get; set; }
- public Stream DetourFile { get; set; }
- }
-
- private Dictionary resourceHookMap = new Dictionary();
-
- public ResourceManager(Dalamud dalamud, SigScanner scanner) {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Dalamud instance.
+ /// The SigScanner instance.
+ public ResourceManager(Dalamud dalamud, SigScanner scanner)
+ {
this.dalamud = dalamud;
- Address = new ResourceManagerAddressResolver();
- Address.Setup(scanner);
+ this.address = new ResourceManagerAddressResolver();
+ this.address.Setup(scanner);
Log.Verbose("===== R E S O U R C E M A N A G E R =====");
- Log.Verbose("GetResourceAsync address {GetResourceAsync}", Address.GetResourceAsync);
- Log.Verbose("GetResourceSync address {GetResourceSync}", Address.GetResourceSync);
+ Log.Verbose("GetResourceAsync address {GetResourceAsync}", this.address.GetResourceAsync);
+ Log.Verbose("GetResourceSync address {GetResourceSync}", this.address.GetResourceSync);
- this.getResourceAsyncHook =
- new Hook(Address.GetResourceAsync,
- new GetResourceAsyncDelegate(GetResourceAsyncDetour),
- this);
-
- this.getResourceSyncHook =
- new Hook(Address.GetResourceSync,
- new GetResourceSyncDelegate(GetResourceSyncDetour),
- this);
-
+ this.getResourceAsyncHook = new Hook(this.address.GetResourceAsync, new GetResourceAsyncDelegate(this.GetResourceAsyncDetour), this);
+
+ this.getResourceSyncHook = new Hook(this.address.GetResourceSync, new GetResourceSyncDelegate(this.GetResourceSyncDetour), this);
}
- public void Enable() {
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6, byte a7);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr GetResourceSyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6);
+
+ ///
+ /// Check if a filepath has any invalid characters.
+ ///
+ /// The filepath to check.
+ /// A value indicating whether the filepath is safe to use.
+ public static bool FilePathHasInvalidChars(string path)
+ {
+ return !string.IsNullOrEmpty(path) && path.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
+ }
+
+ ///
+ /// Enable this module.
+ ///
+ public void Enable()
+ {
this.getResourceAsyncHook.Enable();
this.getResourceSyncHook.Enable();
}
- public void Dispose() {
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ public void Dispose()
+ {
this.getResourceAsyncHook.Dispose();
this.getResourceSyncHook.Dispose();
}
-
- private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7) {
- try {
- var path = Marshal.PtrToStringAnsi(a5);
+ private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6, byte a7)
+ {
+ try
+ {
+ var path = Marshal.PtrToStringAnsi(pathPtr);
var resourceHandle = this.getResourceAsyncHook.Original(manager, a2, a3, a4, IntPtr.Zero, a6, a7);
- //var resourceHandle = IntPtr.Zero;
+ // var resourceHandle = IntPtr.Zero;
- Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, a5, a6, a7, resourceHandle);
+ Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, pathPtr, a6, a7, resourceHandle);
Log.Verbose($"->{path}");
- HandleGetResourceHookAcquire(resourceHandle, path);
+ this.HandleGetResourceHookAcquire(resourceHandle, path);
return resourceHandle;
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Exception on ReadResourceAsync hook.");
- return this.getResourceAsyncHook.Original(manager, a2, a3, a4, a5, a6, a7);
+ return this.getResourceAsyncHook.Original(manager, a2, a3, a4, pathPtr, a6, a7);
}
}
- private void DumpMem(IntPtr address, int len = 512) {
- if (address == IntPtr.Zero)
- return;
+ private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6)
+ {
+ try
+ {
+ var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, pathPtr, a6);
- var data = new byte[len];
- Marshal.Copy(address, data, 0, len);
+ Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, pathPtr, a6, resourceHandle);
- Log.Verbose($"MEMDMP at {address.ToInt64():X} for {len:X}\n{Util.ByteArrayToHex(data)}");
- }
-
- private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6) {
-
- try {
- var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
-
- Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, a5, a6, resourceHandle);
-
- var path = Marshal.PtrToStringAnsi(a5);
+ var path = Marshal.PtrToStringAnsi(pathPtr);
Log.Verbose($"->{path}");
- HandleGetResourceHookAcquire(resourceHandle, path);
+ this.HandleGetResourceHookAcquire(resourceHandle, path);
return resourceHandle;
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Exception on ReadResourceSync hook.");
- return this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
+ return this.getResourceSyncHook.Original(manager, a2, a3, a4, pathPtr, a6);
}
}
- private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path) {
+ private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path)
+ {
if (FilePathHasInvalidChars(path))
return;
- if (this.resourceHookMap.ContainsKey(handlePtr)) {
+ if (this.resourceHookMap.ContainsKey(handlePtr))
+ {
Log.Verbose($"-> Handle {handlePtr.ToInt64():X}({path}) was cached!");
return;
}
- var hookInfo = new ResourceHandleHookInfo {
- Path = path
+ var hookInfo = new ResourceHandleHookInfo
+ {
+ Path = path,
};
var hookPath = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "ResourceHook", path);
- if (System.IO.File.Exists(hookPath)) {
+ if (System.IO.File.Exists(hookPath))
+ {
hookInfo.DetourFile = new FileStream(hookPath, FileMode.Open);
Log.Verbose("-> Added resource hook detour at {0}", hookPath);
}
@@ -137,10 +150,11 @@ namespace Dalamud.Game.Internal.File
this.resourceHookMap.Add(handlePtr, hookInfo);
}
- public static bool FilePathHasInvalidChars(string path)
+ private class ResourceHandleHookInfo
{
+ public string Path { get; set; }
- return (!string.IsNullOrEmpty(path) && path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0);
+ public Stream DetourFile { get; set; }
}
}
}
diff --git a/Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs b/Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs
index 2be49b4ac..b92ea8209 100644
--- a/Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs
+++ b/Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs
@@ -1,20 +1,28 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Dalamud.Game.Internal.File
{
- class ResourceManagerAddressResolver : BaseAddressResolver
+ ///
+ /// The address resolver for the class.
+ ///
+ internal class ResourceManagerAddressResolver : BaseAddressResolver
{
+ ///
+ /// Gets the address of the GetResourceAsync method.
+ ///
public IntPtr GetResourceAsync { get; private set; }
+
+ ///
+ /// Gets the address of the GetResourceSync method.
+ ///
public IntPtr GetResourceSync { get; private set; }
- protected override void Setup64Bit(SigScanner sig) {
- GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74");
- GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6");
- //ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
+ ///
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ this.GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74");
+ this.GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6");
+ // ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
}
}
}
diff --git a/Dalamud/Game/Network/MarketBoardItemRequest.cs b/Dalamud/Game/Network/MarketBoardItemRequest.cs
index 7ec9afe90..df08ce09e 100644
--- a/Dalamud/Game/Network/MarketBoardItemRequest.cs
+++ b/Dalamud/Game/Network/MarketBoardItemRequest.cs
@@ -1,16 +1,21 @@
using System.Collections.Generic;
+
using Dalamud.Game.Network.Structures;
-namespace Dalamud.Game.Network {
- internal class MarketBoardItemRequest {
+namespace Dalamud.Game.Network
+{
+ internal class MarketBoardItemRequest
+ {
public uint CatalogId { get; set; }
+
public byte AmountToArrive { get; set; }
public List Listings { get; set; }
+
public List History { get; set; }
public int ListingsRequestId { get; set; } = -1;
- public bool IsDone => Listings.Count == AmountToArrive && History.Count != 0;
+ public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0;
}
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs b/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs
index cd675d865..c6f2f0303 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs
@@ -1,8 +1,22 @@
using Dalamud.Game.Network.Structures;
-namespace Dalamud.Game.Network.MarketBoardUploaders {
- internal interface IMarketBoardUploader {
- void Upload(MarketBoardItemRequest itemRequest);
+namespace Dalamud.Game.Network.MarketBoardUploaders
+{
+ ///
+ /// An interface binding for the Universalis uploader.
+ ///
+ internal interface IMarketBoardUploader
+ {
+ ///
+ /// Upload data about an item.
+ ///
+ /// The item request data being uploaded.
+ void Upload(MarketBoardItemRequest item);
+
+ ///
+ /// Upload tax rate data.
+ ///
+ /// The tax rate data being uploaded.
void UploadTax(MarketTaxRates taxRates);
}
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryEntry.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryEntry.cs
index f73b9da3e..ba60d484d 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryEntry.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryEntry.cs
@@ -1,28 +1,57 @@
using Newtonsoft.Json;
-namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
- internal class UniversalisHistoryEntry {
+namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
+{
+ ///
+ /// A Universalis API structure.
+ ///
+ internal class UniversalisHistoryEntry
+ {
+ ///
+ /// Gets or sets a value indicating whether the item is HQ or not.
+ ///
[JsonProperty("hq")]
public bool Hq { get; set; }
+ ///
+ /// Gets or sets the item price per unit.
+ ///
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
+ ///
+ /// Gets or sets the quantity of items available.
+ ///
[JsonProperty("quantity")]
public uint Quantity { get; set; }
+ ///
+ /// Gets or sets the name of the buyer.
+ ///
[JsonProperty("buyerName")]
public string BuyerName { get; set; }
+ ///
+ /// Gets or sets a value indicating whether this item was on a mannequin.
+ ///
[JsonProperty("onMannequin")]
public bool OnMannequin { get; set; }
+ ///
+ /// Gets or sets the seller ID.
+ ///
[JsonProperty("sellerID")]
public string SellerId { get; set; }
+ ///
+ /// Gets or sets the buyer ID.
+ ///
[JsonProperty("buyerID")]
public string BuyerId { get; set; }
+ ///
+ /// Gets or sets the timestamp of the transaction.
+ ///
[JsonProperty("timestamp")]
public long Timestamp { get; set; }
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryUploadRequest.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryUploadRequest.cs
index d1f2bb327..1c92171af 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryUploadRequest.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisHistoryUploadRequest.cs
@@ -1,17 +1,35 @@
using System.Collections.Generic;
+
using Newtonsoft.Json;
-namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
- internal class UniversalisHistoryUploadRequest {
+namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
+{
+ ///
+ /// A Universalis API structure.
+ ///
+ internal class UniversalisHistoryUploadRequest
+ {
+ ///
+ /// Gets or sets the world ID.
+ ///
[JsonProperty("worldID")]
public uint WorldId { get; set; }
+ ///
+ /// Gets or sets the item ID.
+ ///
[JsonProperty("itemID")]
public uint ItemId { get; set; }
+ ///
+ /// Gets or sets the list of available entries.
+ ///
[JsonProperty("entries")]
public List Entries { get; set; }
+ ///
+ /// Gets or sets the uploader ID.
+ ///
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsEntry.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsEntry.cs
index 9aa55f1a0..745c2a66e 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsEntry.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsEntry.cs
@@ -1,47 +1,95 @@
using System.Collections.Generic;
+
using Newtonsoft.Json;
-namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
- internal class UniversalisItemListingsEntry {
+namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
+{
+ ///
+ /// A Universalis API structure.
+ ///
+ internal class UniversalisItemListingsEntry
+ {
+ ///
+ /// Gets or sets the listing ID.
+ ///
[JsonProperty("listingID")]
public string ListingId { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the item is HQ.
+ ///
[JsonProperty("hq")]
public bool Hq { get; set; }
+ ///
+ /// Gets or sets the item price per unit.
+ ///
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
+ ///
+ /// Gets or sets the item quantity.
+ ///
[JsonProperty("quantity")]
public uint Quantity { get; set; }
+ ///
+ /// Gets or sets the name of the retainer selling the item.
+ ///
[JsonProperty("retainerName")]
public string RetainerName { get; set; }
+ ///
+ /// Gets or sets the ID of the retainer selling the item.
+ ///
[JsonProperty("retainerID")]
public string RetainerId { get; set; }
+ ///
+ /// Gets or sets the name of the user who created the entry.
+ ///
[JsonProperty("creatorName")]
public string CreatorName { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the item is on a mannequin.
+ ///
[JsonProperty("onMannequin")]
public bool OnMannequin { get; set; }
+ ///
+ /// Gets or sets the seller ID.
+ ///
[JsonProperty("sellerID")]
public string SellerId { get; set; }
+ ///
+ /// Gets or sets the ID of the user who created the entry.
+ ///
[JsonProperty("creatorID")]
public string CreatorId { get; set; }
+ ///
+ /// Gets or sets the ID of the dye on the item.
+ ///
[JsonProperty("stainID")]
public int StainId { get; set; }
+ ///
+ /// Gets or sets the city where the selling retainer resides.
+ ///
[JsonProperty("retainerCity")]
public int RetainerCity { get; set; }
+ ///
+ /// Gets or sets the last time the entry was reviewed.
+ ///
[JsonProperty("lastReviewTime")]
public long LastReviewTime { get; set; }
+ ///
+ /// Gets or sets the materia attached to the item.
+ ///
[JsonProperty("materia")]
public List Materia { get; set; }
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsUploadRequest.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsUploadRequest.cs
index 17e7e77aa..3623769c9 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsUploadRequest.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingsUploadRequest.cs
@@ -1,17 +1,35 @@
using System.Collections.Generic;
+
using Newtonsoft.Json;
-namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
- internal class UniversalisItemListingsUploadRequest {
+namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
+{
+ ///
+ /// A Universalis API structure.
+ ///
+ internal class UniversalisItemListingsUploadRequest
+ {
+ ///
+ /// Gets or sets the world ID.
+ ///
[JsonProperty("worldID")]
public uint WorldId { get; set; }
+ ///
+ /// Gets or sets the item ID.
+ ///
[JsonProperty("itemID")]
public uint ItemId { get; set; }
+ ///
+ /// Gets or sets the list of available items.
+ ///
[JsonProperty("listings")]
public List Listings { get; set; }
+ ///
+ /// Gets or sets the uploader ID.
+ ///
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemMateria.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemMateria.cs
index 93742b84b..5c2cae7c6 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemMateria.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemMateria.cs
@@ -1,10 +1,21 @@
using Newtonsoft.Json;
-namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
- internal class UniversalisItemMateria {
+namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
+{
+ ///
+ /// A Universalis API structure.
+ ///
+ internal class UniversalisItemMateria
+ {
+ ///
+ /// Gets or sets the item slot ID.
+ ///
[JsonProperty("slotID")]
public int SlotId { get; set; }
+ ///
+ /// Gets or sets the materia ID.
+ ///
[JsonProperty("materiaID")]
public int MateriaId { get; set; }
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs
index c051ade85..cffa73491 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs
@@ -1,117 +1,140 @@
using System;
using System.Collections.Generic;
using System.Net;
+
using Dalamud.Game.Network.MarketBoardUploaders;
using Dalamud.Game.Network.MarketBoardUploaders.Universalis;
using Dalamud.Game.Network.Structures;
using Newtonsoft.Json;
using Serilog;
-namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
- internal class UniversalisMarketBoardUploader : IMarketBoardUploader {
+namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders
+{
+ ///
+ /// This class represents an uploader for contributing data to Universalis.
+ ///
+ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
+ {
private const string ApiBase = "https://universalis.app";
- //private const string ApiBase = "https://127.0.0.1:443";
+ // private const string ApiBase = "https://127.0.0.1:443";
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
private readonly Dalamud dalamud;
- public UniversalisMarketBoardUploader(Dalamud dalamud) {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Dalamud instance.
+ public UniversalisMarketBoardUploader(Dalamud dalamud)
+ {
this.dalamud = dalamud;
}
- public void Upload(MarketBoardItemRequest request) {
- using (var client = new WebClient()) {
- client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
+ ///
+ public void Upload(MarketBoardItemRequest request)
+ {
+ using var client = new WebClient();
- Log.Verbose("Starting Universalis upload.");
- var uploader = this.dalamud.ClientState.LocalContentId;
+ client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
- var listingsRequestObject = new UniversalisItemListingsUploadRequest();
- listingsRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
- listingsRequestObject.UploaderId = uploader.ToString();
- listingsRequestObject.ItemId = request.CatalogId;
+ Log.Verbose("Starting Universalis upload.");
+ var uploader = this.dalamud.ClientState.LocalContentId;
- listingsRequestObject.Listings = new List();
- foreach (var marketBoardItemListing in request.Listings) {
- var universalisListing = new UniversalisItemListingsEntry {
- Hq = marketBoardItemListing.IsHq,
- SellerId = marketBoardItemListing.RetainerOwnerId.ToString(),
- RetainerName = marketBoardItemListing.RetainerName,
- RetainerId = marketBoardItemListing.RetainerId.ToString(),
- CreatorId = marketBoardItemListing.ArtisanId.ToString(),
- CreatorName = marketBoardItemListing.PlayerName,
- OnMannequin = marketBoardItemListing.OnMannequin,
- LastReviewTime = ((DateTimeOffset) marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
- PricePerUnit = marketBoardItemListing.PricePerUnit,
- Quantity = marketBoardItemListing.ItemQuantity,
- RetainerCity = marketBoardItemListing.RetainerCityId
- };
+ var listingsRequestObject = new UniversalisItemListingsUploadRequest();
+ listingsRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
+ listingsRequestObject.UploaderId = uploader.ToString();
+ listingsRequestObject.ItemId = request.CatalogId;
- universalisListing.Materia = new List();
- foreach (var itemMateria in marketBoardItemListing.Materia)
- universalisListing.Materia.Add(new UniversalisItemMateria {
- MateriaId = itemMateria.MateriaId,
- SlotId = itemMateria.Index
- });
-
- listingsRequestObject.Listings.Add(universalisListing);
- }
-
- var upload = JsonConvert.SerializeObject(listingsRequestObject);
- client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload);
- Log.Verbose(upload);
-
- var historyRequestObject = new UniversalisHistoryUploadRequest();
- historyRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
- historyRequestObject.UploaderId = uploader.ToString();
- historyRequestObject.ItemId = request.CatalogId;
-
- historyRequestObject.Entries = new List();
- foreach (var marketBoardHistoryListing in request.History)
- historyRequestObject.Entries.Add(new UniversalisHistoryEntry {
- BuyerName = marketBoardHistoryListing.BuyerName,
- Hq = marketBoardHistoryListing.IsHq,
- OnMannequin = marketBoardHistoryListing.OnMannequin,
- PricePerUnit = marketBoardHistoryListing.SalePrice,
- Quantity = marketBoardHistoryListing.Quantity,
- Timestamp = ((DateTimeOffset) marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds()
- });
-
- client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
-
- var historyUpload = JsonConvert.SerializeObject(historyRequestObject);
- client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
- Log.Verbose(historyUpload);
-
- Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId);
- }
- }
-
- public void UploadTax(MarketTaxRates taxRates) {
- using (var client = new WebClient())
+ listingsRequestObject.Listings = new List();
+ foreach (var marketBoardItemListing in request.Listings)
{
- var taxRatesRequest = new UniversalisTaxUploadRequest();
- taxRatesRequest.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
- taxRatesRequest.UploaderId = this.dalamud.ClientState.LocalContentId.ToString();
-
- taxRatesRequest.TaxData = new UniversalisTaxData {
- LimsaLominsa = taxRates.LimsaLominsaTax,
- Gridania = taxRates.GridaniaTax,
- Uldah = taxRates.UldahTax,
- Ishgard = taxRates.IshgardTax,
- Kugane = taxRates.KuganeTax,
- Crystarium = taxRates.CrystariumTax
+ var universalisListing = new UniversalisItemListingsEntry
+ {
+ Hq = marketBoardItemListing.IsHq,
+ SellerId = marketBoardItemListing.RetainerOwnerId.ToString(),
+ RetainerName = marketBoardItemListing.RetainerName,
+ RetainerId = marketBoardItemListing.RetainerId.ToString(),
+ CreatorId = marketBoardItemListing.ArtisanId.ToString(),
+ CreatorName = marketBoardItemListing.PlayerName,
+ OnMannequin = marketBoardItemListing.OnMannequin,
+ LastReviewTime = ((DateTimeOffset)marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
+ PricePerUnit = marketBoardItemListing.PricePerUnit,
+ Quantity = marketBoardItemListing.ItemQuantity,
+ RetainerCity = marketBoardItemListing.RetainerCityId,
};
- client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
+ universalisListing.Materia = new List();
+ foreach (var itemMateria in marketBoardItemListing.Materia)
+ {
+ universalisListing.Materia.Add(new UniversalisItemMateria
+ {
+ MateriaId = itemMateria.MateriaId,
+ SlotId = itemMateria.Index,
+ });
+ }
- var historyUpload = JsonConvert.SerializeObject(taxRatesRequest);
- client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
- Log.Verbose(historyUpload);
-
- Log.Verbose("Universalis tax upload completed.");
+ listingsRequestObject.Listings.Add(universalisListing);
}
+
+ var upload = JsonConvert.SerializeObject(listingsRequestObject);
+ client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload);
+ Log.Verbose(upload);
+
+ var historyRequestObject = new UniversalisHistoryUploadRequest();
+ historyRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
+ historyRequestObject.UploaderId = uploader.ToString();
+ historyRequestObject.ItemId = request.CatalogId;
+
+ historyRequestObject.Entries = new List();
+ foreach (var marketBoardHistoryListing in request.History)
+ {
+ historyRequestObject.Entries.Add(new UniversalisHistoryEntry
+ {
+ BuyerName = marketBoardHistoryListing.BuyerName,
+ Hq = marketBoardHistoryListing.IsHq,
+ OnMannequin = marketBoardHistoryListing.OnMannequin,
+ PricePerUnit = marketBoardHistoryListing.SalePrice,
+ Quantity = marketBoardHistoryListing.Quantity,
+ Timestamp = ((DateTimeOffset)marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds(),
+ });
+ }
+
+ client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
+
+ var historyUpload = JsonConvert.SerializeObject(historyRequestObject);
+ client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
+ Log.Verbose(historyUpload);
+
+ Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId);
+ }
+
+ ///
+ public void UploadTax(MarketTaxRates taxRates)
+ {
+ using var client = new WebClient();
+
+ var taxRatesRequest = new UniversalisTaxUploadRequest();
+ taxRatesRequest.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
+ taxRatesRequest.UploaderId = this.dalamud.ClientState.LocalContentId.ToString();
+
+ taxRatesRequest.TaxData = new UniversalisTaxData
+ {
+ LimsaLominsa = taxRates.LimsaLominsaTax,
+ Gridania = taxRates.GridaniaTax,
+ Uldah = taxRates.UldahTax,
+ Ishgard = taxRates.IshgardTax,
+ Kugane = taxRates.KuganeTax,
+ Crystarium = taxRates.CrystariumTax,
+ };
+
+ client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
+
+ var historyUpload = JsonConvert.SerializeObject(taxRatesRequest);
+ client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
+ Log.Verbose(historyUpload);
+
+ Log.Verbose("Universalis tax upload completed.");
}
}
}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxData.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxData.cs
new file mode 100644
index 000000000..dd56a25ed
--- /dev/null
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxData.cs
@@ -0,0 +1,46 @@
+using Newtonsoft.Json;
+
+namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
+{
+ ///
+ /// A Universalis API structure.
+ ///
+ internal class UniversalisTaxData
+ {
+ ///
+ /// Gets or sets Limsa Lominsa's current tax rate.
+ ///
+ [JsonProperty("limsaLominsa")]
+ public uint LimsaLominsa { get; set; }
+
+ ///
+ /// Gets or sets Gridania's current tax rate.
+ ///
+ [JsonProperty("gridania")]
+ public uint Gridania { get; set; }
+
+ ///
+ /// Gets or sets Ul'dah's current tax rate.
+ ///
+ [JsonProperty("uldah")]
+ public uint Uldah { get; set; }
+
+ ///
+ /// Gets or sets Ishgard's current tax rate.
+ ///
+ [JsonProperty("ishgard")]
+ public uint Ishgard { get; set; }
+
+ ///
+ /// Gets or sets Kugane's current tax rate.
+ ///
+ [JsonProperty("kugane")]
+ public uint Kugane { get; set; }
+
+ ///
+ /// Gets or sets The Crystarium's current tax rate.
+ ///
+ [JsonProperty("crystarium")]
+ public uint Crystarium { get; set; }
+ }
+}
diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxUploadRequest.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxUploadRequest.cs
index 7a82db06c..bdf9715a0 100644
--- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxUploadRequest.cs
+++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxUploadRequest.cs
@@ -1,41 +1,28 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
{
- class UniversalisTaxUploadRequest
+ ///
+ /// A Universalis API structure.
+ ///
+ internal class UniversalisTaxUploadRequest
{
+ ///
+ /// Gets or sets the uploader's ID.
+ ///
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
+ ///
+ /// Gets or sets the world to retrieve data from.
+ ///
[JsonProperty("worldID")]
public uint WorldId { get; set; }
+ ///
+ /// Gets or sets tax data for each city's market.
+ ///
[JsonProperty("marketTaxRates")]
public UniversalisTaxData TaxData { get; set; }
}
-
- class UniversalisTaxData {
- [JsonProperty("limsaLominsa")]
- public uint LimsaLominsa { get; set; }
-
- [JsonProperty("gridania")]
- public uint Gridania { get; set; }
-
- [JsonProperty("uldah")]
- public uint Uldah { get; set; }
-
- [JsonProperty("ishgard")]
- public uint Ishgard { get; set; }
-
- [JsonProperty("kugane")]
- public uint Kugane { get; set; }
-
- [JsonProperty("crystarium")]
- public uint Crystarium { get; set; }
- }
}
diff --git a/Dalamud/Game/Network/NetworkHandlers.cs b/Dalamud/Game/Network/NetworkHandlers.cs
index face2eaee..790ed57d5 100644
--- a/Dalamud/Game/Network/NetworkHandlers.cs
+++ b/Dalamud/Game/Network/NetworkHandlers.cs
@@ -4,47 +4,58 @@ using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
+
using Dalamud.Game.Internal.Network;
using Dalamud.Game.Network.MarketBoardUploaders;
using Dalamud.Game.Network.Structures;
using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
-using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
-using Newtonsoft.Json.Linq;
using Serilog;
-namespace Dalamud.Game.Network {
- public class NetworkHandlers {
+namespace Dalamud.Game.Network
+{
+ ///
+ /// This class handles network notifications and uploading Marketboard data.
+ ///
+ public class NetworkHandlers
+ {
private readonly Dalamud dalamud;
- private readonly List marketBoardRequests = new List();
+ private readonly List marketBoardRequests = new();
private readonly bool optOutMbUploads;
private readonly IMarketBoardUploader uploader;
///
- /// Event which gets fired when a duty is ready.
+ /// Initializes a new instance of the class.
///
- public event EventHandler CfPop;
-
- public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads) {
+ /// The Dalamud instance.
+ /// Whether the client should opt out of marketboard uploads.
+ public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads)
+ {
this.dalamud = dalamud;
this.optOutMbUploads = optOutMbUploads;
this.uploader = new UniversalisMarketBoardUploader(dalamud);
- dalamud.Framework.Network.OnNetworkMessage += OnNetworkMessage;
-
+ dalamud.Framework.Network.OnNetworkMessage += this.OnNetworkMessage;
}
- private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) {
+ ///
+ /// Event which gets fired when a duty is ready.
+ ///
+ public event EventHandler CfPop;
+
+ private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction)
+ {
if (direction != NetworkMessageDirection.ZoneDown)
return;
if (!this.dalamud.Data.IsDataReady)
return;
- if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) {
+ if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"])
+ {
var data = new byte[64];
Marshal.Copy(dataPtr, data, 0, 64);
@@ -63,98 +74,114 @@ namespace Dalamud.Game.Network {
}
var cfcName = contentFinderCondition.Name.ToString();
- if (string.IsNullOrEmpty(contentFinderCondition.Name)) {
+ if (string.IsNullOrEmpty(contentFinderCondition.Name))
+ {
cfcName = "Duty Roulette";
contentFinderCondition.Image = 112324;
}
- if (this.dalamud.Configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) {
- var flashInfo = new NativeFunctions.FLASHWINFO
+ if (this.dalamud.Configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated())
+ {
+ var flashInfo = new NativeFunctions.FlashWindowInfo
{
- cbSize = (uint)Marshal.SizeOf(),
+ cbSize = (uint)Marshal.SizeOf(),
uCount = uint.MaxValue,
dwTimeout = 0,
- dwFlags = NativeFunctions.FlashWindow.FLASHW_ALL |
- NativeFunctions.FlashWindow.FLASHW_TIMERNOFG,
- hwnd = Process.GetCurrentProcess().MainWindowHandle
+ dwFlags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG,
+ hwnd = Process.GetCurrentProcess().MainWindowHandle,
};
NativeFunctions.FlashWindowEx(ref flashInfo);
}
- Task.Run(() => {
- if(this.dalamud.Configuration.DutyFinderChatMessage)
+ Task.Run(() =>
+ {
+ if (this.dalamud.Configuration.DutyFinderChatMessage)
this.dalamud.Framework.Gui.Chat.Print("Duty pop: " + cfcName);
- CfPop?.Invoke(this, contentFinderCondition);
+ this.CfPop?.Invoke(this, contentFinderCondition);
});
return;
}
- if (!this.optOutMbUploads) {
- if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"]) {
- var catalogId = (uint) Marshal.ReadInt32(dataPtr);
+ if (!this.optOutMbUploads)
+ {
+ if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"])
+ {
+ var catalogId = (uint)Marshal.ReadInt32(dataPtr);
var amount = Marshal.ReadByte(dataPtr + 0xB);
- this.marketBoardRequests.Add(new MarketBoardItemRequest {
+ this.marketBoardRequests.Add(new MarketBoardItemRequest
+ {
CatalogId = catalogId,
AmountToArrive = amount,
Listings = new List(),
- History = new List()
+ History = new List(),
});
Log.Verbose($"NEW MB REQUEST START: item#{catalogId} amount#{amount}");
return;
}
- if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"]) {
+ if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"])
+ {
var listing = MarketBoardCurrentOfferings.Read(dataPtr);
- var request =
- this.marketBoardRequests.LastOrDefault(
- r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
+ var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
- if (request == null) {
- Log.Error(
- $"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
+ if (request == null)
+ {
+ Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
return;
}
- if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) {
- Log.Error(
- $"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
+ if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive)
+ {
+ Log.Error($"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
return;
}
- if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) {
- Log.Error(
- $"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
+ if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId)
+ {
+ Log.Error($"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
return;
}
- if (request.ListingsRequestId == -1 && request.Listings.Count > 0) {
- Log.Error(
- $"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
+ if (request.ListingsRequestId == -1 && request.Listings.Count > 0)
+ {
+ Log.Error($"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
return;
}
- if (request.ListingsRequestId == -1) {
+ if (request.ListingsRequestId == -1)
+ {
request.ListingsRequestId = listing.RequestId;
Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}");
}
request.Listings.AddRange(listing.ItemListings);
- Log.Verbose("Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
- listing.ItemListings.Count, request.ListingsRequestId, request.Listings.Count,
- request.AmountToArrive, request.CatalogId);
+ Log.Verbose(
+ "Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
+ listing.ItemListings.Count,
+ request.ListingsRequestId,
+ request.Listings.Count,
+ request.AmountToArrive,
+ request.CatalogId);
- if (request.IsDone) {
- Log.Verbose("Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
- request.ListingsRequestId, request.CatalogId, request.AmountToArrive);
- try {
+ if (request.IsDone)
+ {
+ Log.Verbose(
+ "Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
+ request.ListingsRequestId,
+ request.CatalogId,
+ request.AmountToArrive);
+ try
+ {
Task.Run(() => this.uploader.Upload(request));
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Market Board data upload failed.");
}
}
@@ -162,18 +189,21 @@ namespace Dalamud.Game.Network {
return;
}
- if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"]) {
+ if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"])
+ {
var listing = MarketBoardHistory.Read(dataPtr);
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId);
- if (request == null) {
+ if (request == null)
+ {
Log.Error(
$"Market Board data arrived without a corresponding request: item#{listing.CatalogId}");
return;
}
- if (request.ListingsRequestId != -1) {
+ if (request.ListingsRequestId != -1)
+ {
Log.Error(
$"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
return;
@@ -183,7 +213,8 @@ namespace Dalamud.Game.Network {
Log.Verbose("Added history for item#{0}", listing.CatalogId);
- if (request.AmountToArrive == 0) {
+ if (request.AmountToArrive == 0)
+ {
Log.Verbose("Request had 0 amount, uploading now");
try
@@ -197,17 +228,25 @@ namespace Dalamud.Game.Network {
}
}
- if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"]) {
- var category = (uint) Marshal.ReadInt32(dataPtr);
+ if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"])
+ {
+ var category = (uint)Marshal.ReadInt32(dataPtr);
// Result dialog packet does not contain market tax rates
- if (category != 720905) {
+ if (category != 720905)
+ {
return;
}
var taxes = MarketTaxRates.Read(dataPtr);
- Log.Verbose("MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
- taxes.LimsaLominsaTax, taxes.GridaniaTax, taxes.UldahTax, taxes.IshgardTax, taxes.KuganeTax, taxes.CrystariumTax);
+ Log.Verbose(
+ "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
+ taxes.LimsaLominsaTax,
+ taxes.GridaniaTax,
+ taxes.UldahTax,
+ taxes.IshgardTax,
+ taxes.KuganeTax,
+ taxes.CrystariumTax);
try
{
Task.Run(() => this.uploader.UploadTax(taxes));
diff --git a/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs b/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs
index 7d9f72cc8..2ac0f8886 100644
--- a/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs
+++ b/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs
@@ -17,69 +17,66 @@ namespace Dalamud.Game.Network.Structures
{
var output = new MarketBoardCurrentOfferings();
- using (var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544))
+ using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
+ using var reader = new BinaryReader(stream);
+
+ output.ItemListings = new List();
+
+ for (var i = 0; i < 10; i++)
{
- using (var reader = new BinaryReader(stream))
+ var listingEntry = new MarketBoardItemListing();
+
+ listingEntry.ListingId = reader.ReadUInt64();
+ listingEntry.RetainerId = reader.ReadUInt64();
+ listingEntry.RetainerOwnerId = reader.ReadUInt64();
+ listingEntry.ArtisanId = reader.ReadUInt64();
+ listingEntry.PricePerUnit = reader.ReadUInt32();
+ listingEntry.TotalTax = reader.ReadUInt32();
+ listingEntry.ItemQuantity = reader.ReadUInt32();
+ listingEntry.CatalogId = reader.ReadUInt32();
+ listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime;
+
+ reader.ReadUInt16(); // container
+ reader.ReadUInt32(); // slot
+ reader.ReadUInt16(); // durability
+ reader.ReadUInt16(); // spiritbond
+
+ listingEntry.Materia = new List();
+
+ for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++)
{
- output.ItemListings = new List();
+ var materiaVal = reader.ReadUInt16();
- for (var i = 0; i < 10; i++)
- {
- var listingEntry = new MarketBoardItemListing();
+ var materiaEntry = new MarketBoardItemListing.ItemMateria();
+ materiaEntry.MateriaId = (materiaVal & 0xFF0) >> 4;
+ materiaEntry.Index = materiaVal & 0xF;
- listingEntry.ListingId = reader.ReadUInt64();
- listingEntry.RetainerId = reader.ReadUInt64();
- listingEntry.RetainerOwnerId = reader.ReadUInt64();
- listingEntry.ArtisanId = reader.ReadUInt64();
- listingEntry.PricePerUnit = reader.ReadUInt32();
- listingEntry.TotalTax = reader.ReadUInt32();
- listingEntry.ItemQuantity = reader.ReadUInt32();
- listingEntry.CatalogId = reader.ReadUInt32();
- listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime;
-
- reader.ReadUInt16(); // container
- reader.ReadUInt32(); // slot
- reader.ReadUInt16(); // durability
- reader.ReadUInt16(); // spiritbond
-
- listingEntry.Materia = new List();
-
- for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++)
- {
- var materiaVal = reader.ReadUInt16();
-
- var materiaEntry = new MarketBoardItemListing.ItemMateria();
- materiaEntry.MateriaId = (materiaVal & 0xFF0) >> 4;
- materiaEntry.Index = materiaVal & 0xF;
-
- if (materiaEntry.MateriaId != 0)
- listingEntry.Materia.Add(materiaEntry);
- }
-
- reader.ReadUInt16();
- reader.ReadUInt32();
-
- listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
- listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
- listingEntry.IsHq = reader.ReadBoolean();
- listingEntry.MateriaCount = reader.ReadByte();
- listingEntry.OnMannequin = reader.ReadBoolean();
- listingEntry.RetainerCityId = reader.ReadByte();
- listingEntry.StainId = reader.ReadUInt16();
-
- reader.ReadUInt16();
- reader.ReadUInt32();
-
- if (listingEntry.CatalogId != 0)
- output.ItemListings.Add(listingEntry);
- }
-
- output.ListingIndexEnd = reader.ReadByte();
- output.ListingIndexStart = reader.ReadByte();
- output.RequestId = reader.ReadUInt16();
+ if (materiaEntry.MateriaId != 0)
+ listingEntry.Materia.Add(materiaEntry);
}
+
+ reader.ReadUInt16();
+ reader.ReadUInt32();
+
+ listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
+ listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
+ listingEntry.IsHq = reader.ReadBoolean();
+ listingEntry.MateriaCount = reader.ReadByte();
+ listingEntry.OnMannequin = reader.ReadBoolean();
+ listingEntry.RetainerCityId = reader.ReadByte();
+ listingEntry.StainId = reader.ReadUInt16();
+
+ reader.ReadUInt16();
+ reader.ReadUInt32();
+
+ if (listingEntry.CatalogId != 0)
+ output.ItemListings.Add(listingEntry);
}
+ output.ListingIndexEnd = reader.ReadByte();
+ output.ListingIndexStart = reader.ReadByte();
+ output.RequestId = reader.ReadUInt16();
+
return output;
}
diff --git a/Dalamud/Game/Network/Structures/MarketBoardHistory.cs b/Dalamud/Game/Network/Structures/MarketBoardHistory.cs
index c96b4a0e0..b2f3e3ad0 100644
--- a/Dalamud/Game/Network/Structures/MarketBoardHistory.cs
+++ b/Dalamud/Game/Network/Structures/MarketBoardHistory.cs
@@ -3,47 +3,52 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
-namespace Dalamud.Game.Network.Structures {
- public class MarketBoardHistory {
+namespace Dalamud.Game.Network.Structures
+{
+ public class MarketBoardHistory
+ {
public uint CatalogId;
public uint CatalogId2;
public List