From 595fd3f1e49d558ba624a3756eb54178a80cf08b Mon Sep 17 00:00:00 2001 From: Raymond Lynch Date: Sun, 30 May 2021 07:10:00 -0400 Subject: [PATCH] StyleCop: everything else --- Dalamud/Configuration/PluginConfigurations.cs | 9 +- Dalamud/Dalamud.cs | 18 +- Dalamud/Dalamud.csproj | 16 +- Dalamud/DalamudStartInfo.cs | 3 - Dalamud/Data/DataManager.cs | 190 +- Dalamud/EntryPoint.cs | 4 +- Dalamud/Game/Addon/DalamudSystemMenu.cs | 76 +- Dalamud/Game/ChatHandlers.cs | 264 +- Dalamud/Game/ClientState/Actors/ActorTable.cs | 246 +- .../Game/ClientState/Actors/CustomizeIndex.cs | 23 +- Dalamud/Game/ClientState/Actors/ObjectKind.cs | 46 +- Dalamud/Game/ClientState/Actors/Position3.cs | 28 +- .../Actors/Resolvers/BaseResolver.cs | 27 +- .../ClientState/Actors/Resolvers/ClassJob.cs | 29 +- .../ClientState/Actors/Resolvers/World.cs | 27 +- Dalamud/Game/ClientState/Actors/Targets.cs | 134 +- .../Game/ClientState/Actors/Types/Actor.cs | 6 +- .../Game/ClientState/Actors/Types/Chara.cs | 2 +- .../ClientState/Actors/Types/PartyMember.cs | 34 +- .../Actors/Types/PlayerCharacter.cs | 4 +- Dalamud/Game/ClientState/ClientState.cs | 227 +- .../ClientState/ClientStateAddressResolver.cs | 92 +- Dalamud/Game/ClientState/Condition.cs | 40 +- Dalamud/Game/ClientState/ConditionFlag.cs | 36 +- Dalamud/Game/ClientState/GamepadState.cs | 2 +- Dalamud/Game/ClientState/KeyState.cs | 18 +- Dalamud/Game/ClientState/PartyList.cs | 155 +- Dalamud/Game/ClientState/Structs/Actor.cs | 287 +- .../ClientState/Structs/JobGauge/JobEnums.cs | 4 + .../Game/ClientState/Structs/PartyMember.cs | 25 +- .../Game/ClientState/Structs/StatusEffect.cs | 20 +- Dalamud/Game/Command/CommandInfo.cs | 35 +- Dalamud/Game/Command/CommandManager.cs | 124 +- Dalamud/Game/Internal/AntiDebug.cs | 132 +- Dalamud/Game/Internal/BaseAddressResolver.cs | 112 +- .../DXGI/ISwapChainAddressResolver.cs | 16 +- .../Internal/DXGI/SwapChainSigResolver.cs | 15 +- .../Internal/DXGI/SwapChainVtableResolver.cs | 131 +- Dalamud/Game/Internal/Framework.cs | 242 +- .../Game/Internal/FrameworkAddressResolver.cs | 41 +- Dalamud/Game/Internal/Gui/Addon/Addon.cs | 67 +- Dalamud/Game/Internal/Gui/ChatGui.cs | 566 +- .../Internal/Gui/ChatGuiAddressResolver.cs | 189 +- Dalamud/Game/Internal/Gui/GameGui.cs | 671 +- .../Internal/Gui/GameGuiAddressResolver.cs | 137 +- Dalamud/Game/Internal/Gui/HoverActionKind.cs | 39 +- Dalamud/Game/Internal/Gui/HoveredAction.cs | 20 +- .../Gui/PartyFinderAddressResolver.cs | 20 +- Dalamud/Game/Internal/Gui/PartyFinderGui.cs | 160 +- Dalamud/Game/Internal/Gui/Structs/Addon.cs | 68 +- .../Game/Internal/Gui/Structs/AtkResNode.cs | 165 +- .../Game/Internal/Gui/Structs/PartyFinder.cs | 428 +- .../Gui/Structs/PartyFinderListing.cs | 230 + .../Internal/Gui/Structs/PartyFinderSlot.cs | 51 + .../Internal/Gui/Structs/PartyFinderTypes.cs | 397 + Dalamud/Game/Internal/Gui/TargetManager.cs | 54 - .../Gui/TargetManagerAddressResolver.cs | 15 - .../Internal/Gui/Toast/QuestToastOptions.cs | 12 +- .../Internal/Gui/Toast/QuestToastPosition.cs | 16 +- .../Game/Internal/Gui/Toast/ToastOptions.cs | 3 + .../Game/Internal/Gui/Toast/ToastPosition.cs | 12 +- Dalamud/Game/Internal/Gui/Toast/ToastSpeed.cs | 5 +- Dalamud/Game/Internal/Gui/ToastGui.cs | 407 +- .../Internal/Gui/ToastGuiAddressResolver.cs | 15 +- Dalamud/Game/Internal/Libc/LibcFunction.cs | 69 +- .../Libc/LibcFunctionAddressResolver.cs | 29 +- Dalamud/Game/Internal/Libc/OwnedStdString.cs | 117 +- Dalamud/Game/Internal/Libc/StdString.cs | 66 +- Dalamud/Game/Internal/Network/GameNetwork.cs | 175 +- .../Network/GameNetworkAddressResolver.cs | 29 +- .../Network/NetworkMessageDirection.cs | 18 +- .../Game/Internal/Resource/ResourceManager.cs | 156 +- .../ResourceManagerAddressResolver.cs | 26 +- .../Game/Network/MarketBoardItemRequest.cs | 11 +- .../IMarketBoardUploader.cs | 20 +- .../Universalis/UniversalisHistoryEntry.cs | 33 +- .../UniversalisHistoryUploadRequest.cs | 22 +- .../UniversalisItemListingsEntry.cs | 52 +- .../UniversalisItemListingsUploadRequest.cs | 22 +- .../Universalis/UniversalisItemMateria.cs | 15 +- .../UniversalisMarketBoardUploader.cs | 197 +- .../Universalis/UniversalisTaxData.cs | 46 + .../UniversalisTaxUploadRequest.cs | 39 +- Dalamud/Game/Network/NetworkHandlers.cs | 163 +- .../Structures/MarketBoardCurrentOfferings.cs | 109 +- .../Network/Structures/MarketBoardHistory.cs | 51 +- .../Game/Network/Structures/MarketTaxRate.cs | 41 - .../Game/Network/Structures/MarketTaxRates.cs | 34 + Dalamud/Game/Network/WinSockHandlers.cs | 44 +- Dalamud/Game/SigScanner.cs | 8 +- Dalamud/Game/Text/Sanitizer/Sanitizer.cs | 21 +- Dalamud/Game/Text/SeIconChar.cs | 581 +- .../Text/SeStringHandling/BitmapFontIcon.cs | 496 +- .../Text/SeStringHandling/ITextProvider.cs | 8 +- Dalamud/Game/Text/SeStringHandling/Payload.cs | 228 +- .../Game/Text/SeStringHandling/PayloadType.cs | 19 +- .../Payloads/AutoTranslatePayload.cs | 75 +- .../Payloads/DalamudLinkPayload.cs | 54 +- .../Payloads/EmphasisItalicPayload.cs | 58 +- .../SeStringHandling/Payloads/IconPayload.cs | 91 +- .../SeStringHandling/Payloads/ItemPayload.cs | 113 +- .../Payloads/MapLinkPayload.cs | 247 +- .../Payloads/PlayerPayload.cs | 114 +- .../SeStringHandling/Payloads/QuestPayload.cs | 75 +- .../SeStringHandling/Payloads/RawPayload.cs | 112 +- .../Payloads/SeHyphenPayload.cs | 33 +- .../Payloads/StatusPayload.cs | 56 +- .../SeStringHandling/Payloads/TextPayload.cs | 67 +- .../Payloads/UIForegroundPayload.cs | 132 +- .../Payloads/UIGlowPayload.cs | 132 +- .../Game/Text/SeStringHandling/SeString.cs | 124 +- .../Text/SeStringHandling/SeStringManager.cs | 47 +- Dalamud/Game/Text/XivChatEntry.cs | 21 +- Dalamud/Game/Text/XivChatType.cs | 182 +- Dalamud/GlobalSuppressions.cs | 104 +- Dalamud/Hooking/HookInfo.cs | 2 +- Dalamud/Interface/DalamudChangelogWindow.cs | 48 +- Dalamud/Interface/DalamudCreditsWindow.cs | 39 +- Dalamud/Interface/DalamudDataWindow.cs | 51 +- Dalamud/Interface/DalamudInterface.cs | 5 +- Dalamud/Interface/DalamudLogWindow.cs | 113 +- Dalamud/Interface/DalamudPluginStatWindow.cs | 114 +- Dalamud/Interface/DalamudSettingsWindow.cs | 179 +- Dalamud/Interface/FontAwesomeIcon.cs | 8472 ++++++++++++++--- Dalamud/Interface/GlyphRangesJapanese.cs | 14 +- Dalamud/Interface/ImGuiHelpers.cs | 4 +- Dalamud/Interface/InterfaceManager.cs | 401 +- Dalamud/Interface/SerilogEventSink.cs | 56 +- Dalamud/Interface/UiDebug.cs | 549 +- Dalamud/NativeFunctions.cs | 593 +- Dalamud/Plugin/PluginInstallerWindow.cs | 15 +- Dalamud/Plugin/PluginManager.cs | 25 +- Dalamud/Plugin/PluginRepository.cs | 386 +- Dalamud/Util.cs | 13 +- 134 files changed, 16346 insertions(+), 6202 deletions(-) create mode 100644 Dalamud/Game/Internal/Gui/Structs/PartyFinderListing.cs create mode 100644 Dalamud/Game/Internal/Gui/Structs/PartyFinderSlot.cs create mode 100644 Dalamud/Game/Internal/Gui/Structs/PartyFinderTypes.cs delete mode 100644 Dalamud/Game/Internal/Gui/TargetManager.cs delete mode 100644 Dalamud/Game/Internal/Gui/TargetManagerAddressResolver.cs create mode 100644 Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisTaxData.cs delete mode 100644 Dalamud/Game/Network/Structures/MarketTaxRate.cs create mode 100644 Dalamud/Game/Network/Structures/MarketTaxRates.cs 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|VPK\.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 HistoryListings; - public static unsafe MarketBoardHistory Read(IntPtr dataPtr) { + public static unsafe MarketBoardHistory Read(IntPtr dataPtr) + { var output = new MarketBoardHistory(); - using (var stream = new UnmanagedMemoryStream((byte*) dataPtr.ToPointer(), 1544)) { - using (var reader = new BinaryReader(stream)) { - output.CatalogId = reader.ReadUInt32(); - output.CatalogId2 = reader.ReadUInt32(); + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - output.HistoryListings = new List(); + output.CatalogId = reader.ReadUInt32(); + output.CatalogId2 = reader.ReadUInt32(); - for (var i = 0; i < 10; i++) { - var listingEntry = new MarketBoardHistoryListing(); + output.HistoryListings = new List(); - listingEntry.SalePrice = reader.ReadUInt32(); - listingEntry.PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime; - listingEntry.Quantity = reader.ReadUInt32(); - listingEntry.IsHq = reader.ReadBoolean(); + for (var i = 0; i < 10; i++) + { + var listingEntry = new MarketBoardHistoryListing + { + SalePrice = reader.ReadUInt32(), + PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime, + Quantity = reader.ReadUInt32(), + IsHq = reader.ReadBoolean(), + }; - reader.ReadBoolean(); + reader.ReadBoolean(); - listingEntry.OnMannequin = reader.ReadBoolean(); - listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000'); - listingEntry.CatalogId = reader.ReadUInt32(); + listingEntry.OnMannequin = reader.ReadBoolean(); + listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000'); + listingEntry.CatalogId = reader.ReadUInt32(); - if (listingEntry.CatalogId != 0) - output.HistoryListings.Add(listingEntry); - } - } + if (listingEntry.CatalogId != 0) + output.HistoryListings.Add(listingEntry); } return output; } - public class MarketBoardHistoryListing { + public class MarketBoardHistoryListing + { public string BuyerName; public uint CatalogId; diff --git a/Dalamud/Game/Network/Structures/MarketTaxRate.cs b/Dalamud/Game/Network/Structures/MarketTaxRate.cs deleted file mode 100644 index b0ce2772c..000000000 --- a/Dalamud/Game/Network/Structures/MarketTaxRate.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Text; - -namespace Dalamud.Game.Network.Structures -{ - public class MarketTaxRates - { - public uint LimsaLominsaTax; - public uint GridaniaTax; - public uint UldahTax; - public uint IshgardTax; - public uint KuganeTax; - public uint CrystariumTax; - - - public static unsafe MarketTaxRates Read(IntPtr dataPtr) - { - var output = new MarketTaxRates(); - - using (var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544)) - { - using (var reader = new BinaryReader(stream)) - { - stream.Position += 8; - - output.LimsaLominsaTax = reader.ReadUInt32(); - output.GridaniaTax = reader.ReadUInt32(); - output.UldahTax = reader.ReadUInt32(); - output.IshgardTax = reader.ReadUInt32(); - output.KuganeTax = reader.ReadUInt32(); - output.CrystariumTax = reader.ReadUInt32(); - } - } - - return output; - } - } -} diff --git a/Dalamud/Game/Network/Structures/MarketTaxRates.cs b/Dalamud/Game/Network/Structures/MarketTaxRates.cs new file mode 100644 index 000000000..4a79c0242 --- /dev/null +++ b/Dalamud/Game/Network/Structures/MarketTaxRates.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; + +namespace Dalamud.Game.Network.Structures +{ + public class MarketTaxRates + { + public uint LimsaLominsaTax; + public uint GridaniaTax; + public uint UldahTax; + public uint IshgardTax; + public uint KuganeTax; + public uint CrystariumTax; + + public static unsafe MarketTaxRates Read(IntPtr dataPtr) + { + var output = new MarketTaxRates(); + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + + stream.Position += 8; + + output.LimsaLominsaTax = reader.ReadUInt32(); + output.GridaniaTax = reader.ReadUInt32(); + output.UldahTax = reader.ReadUInt32(); + output.IshgardTax = reader.ReadUInt32(); + output.KuganeTax = reader.ReadUInt32(); + output.CrystariumTax = reader.ReadUInt32(); + + return output; + } + } +} diff --git a/Dalamud/Game/Network/WinSockHandlers.cs b/Dalamud/Game/Network/WinSockHandlers.cs index f2d9d9706..8a20bb98f 100644 --- a/Dalamud/Game/Network/WinSockHandlers.cs +++ b/Dalamud/Game/Network/WinSockHandlers.cs @@ -1,28 +1,38 @@ -using Dalamud.Hooking; using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; + +using Dalamud.Hooking; namespace Dalamud.Game { + /// + /// This class enables TCP optimizations in the game socket for better performance. + /// internal sealed class WinSockHandlers : IDisposable { - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate IntPtr SocketDelegate(int af, int type, int protocol); private Hook ws2SocketHook; - [DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi)] - private static extern int setsockopt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen); - - public WinSockHandlers() { - this.ws2SocketHook = Hook.FromSymbol("ws2_32.dll", "socket", new SocketDelegate(OnSocket)); + /// + /// Initializes a new instance of the class. + /// + public WinSockHandlers() + { + this.ws2SocketHook = Hook.FromSymbol("ws2_32.dll", "socket", new SocketDelegate(this.OnSocket)); this.ws2SocketHook.Enable(); } + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate IntPtr SocketDelegate(int af, int type, int protocol); + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.ws2SocketHook.Dispose(); + } + private IntPtr OnSocket(int af, int type, int protocol) { var socket = this.ws2SocketHook.Original(af, type, protocol); @@ -30,26 +40,22 @@ namespace Dalamud.Game // IPPROTO_TCP if (type == 1) { - // INVALID_SOCKET + // INVALID_SOCKET if (socket != new IntPtr(-1)) { // In case you're not aware of it: (albeit you should) // https://linux.die.net/man/7/tcp // https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf var value = new IntPtr(1); - setsockopt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4); + NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4); // Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards. value = new IntPtr(1); - setsockopt(socket, SocketOptionLevel.Tcp, (SocketOptionName)12, ref value, 4); + NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.AddMembership, ref value, 4); } } return socket; } - - public void Dispose() { - ws2SocketHook.Dispose(); - } } } diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 8449098a3..ec1608bec 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -56,7 +56,7 @@ namespace Dalamud.Game /// /// Gets the base address of the .text section search area. /// - public IntPtr TextSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.TextSectionOffset); + public IntPtr TextSectionBase => new(this.SearchBase.ToInt64() + this.TextSectionOffset); /// /// Gets the offset of the .text section from the base of the module. @@ -71,7 +71,7 @@ namespace Dalamud.Game /// /// Gets the base address of the .data section search area. /// - public IntPtr DataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.DataSectionOffset); + public IntPtr DataSectionBase => new(this.SearchBase.ToInt64() + this.DataSectionOffset); /// /// Gets the offset of the .data section from the base of the module. @@ -86,7 +86,7 @@ namespace Dalamud.Game /// /// Gets the base address of the .rdata section search area. /// - public IntPtr RDataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.RDataSectionOffset); + public IntPtr RDataSectionBase => new(this.SearchBase.ToInt64() + this.RDataSectionOffset); /// /// Gets the offset of the .rdata section from the base of the module. @@ -230,7 +230,7 @@ namespace Dalamud.Game return IntPtr.Add(sigLocation, 5 + jumpOffset); } - private static (byte[] needle, bool[] mask) ParseSignature(string signature) + private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) { signature = signature.Replace(" ", string.Empty); if (signature.Length % 2 != 0) diff --git a/Dalamud/Game/Text/Sanitizer/Sanitizer.cs b/Dalamud/Game/Text/Sanitizer/Sanitizer.cs index c1f91f99c..ea8cd0000 100644 --- a/Dalamud/Game/Text/Sanitizer/Sanitizer.cs +++ b/Dalamud/Game/Text/Sanitizer/Sanitizer.cs @@ -9,12 +9,12 @@ namespace Dalamud.Game.Text.Sanitizer /// public class Sanitizer : ISanitizer { - private static readonly Dictionary DESanitizationDict = new Dictionary + private static readonly Dictionary DESanitizationDict = new() { { "\u0020\u2020", string.Empty }, // dagger }; - private static readonly Dictionary FRSanitizationDict = new Dictionary + private static readonly Dictionary FRSanitizationDict = new() { { "\u0153", "\u006F\u0065" }, // ligature oe }; @@ -75,18 +75,13 @@ namespace Dalamud.Game.Text.Sanitizer private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage) { var sanitizedString = FilterUnprintableCharacters(unsanitizedString); - switch (clientLanguage) + return clientLanguage switch { - case ClientLanguage.Japanese: - case ClientLanguage.English: - return sanitizedString; - case ClientLanguage.German: - return FilterByDict(sanitizedString, DESanitizationDict); - case ClientLanguage.French: - return FilterByDict(sanitizedString, FRSanitizationDict); - default: - throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null); - } + ClientLanguage.Japanese or ClientLanguage.English => sanitizedString, + ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict), + ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict), + _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), + }; } private static IEnumerable SanitizeByLanguage( diff --git a/Dalamud/Game/Text/SeIconChar.cs b/Dalamud/Game/Text/SeIconChar.cs index 8fa0cfd20..539868135 100644 --- a/Dalamud/Game/Text/SeIconChar.cs +++ b/Dalamud/Game/Text/SeIconChar.cs @@ -1,182 +1,743 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -#pragma warning disable 1591 - namespace Dalamud.Game.Text { /// /// Special unicode characters with game-related symbols that work both in-game and in any dalamud window. /// - public enum SeIconChar { + public enum SeIconChar + { + /// + /// The sprout icon unicode character. + /// BotanistSprout = 0xE034, + + /// + /// The item level icon unicode character. + /// ItemLevel = 0xE033, + + /// + /// The auto translate open icon unicode character. + /// AutoTranslateOpen = 0xE040, + + /// + /// The auto translate close icon unicode character. + /// AutoTranslateClose = 0xE041, + + /// + /// The high quality icon unicode character. + /// HighQuality = 0xE03C, + + /// + /// The clock icon unicode character. + /// Clock = 0xE031, + + /// + /// The gil icon unicode character. + /// Gil = 0xE049, + + /// + /// The Hydaelyn icon unicode character. + /// Hyadelyn = 0xE048, + /// + /// The no mouse click icon unicode character. + /// MouseNoClick = 0xE050, + + /// + /// The left mouse click icon unicode character. + /// MouseLeftClick = 0xE051, + + /// + /// The right mouse click icon unicode character. + /// MouseRightClick = 0xE052, + + /// + /// The left/right mouse click icon unicode character. + /// MouseBothClick = 0xE053, + + /// + /// The mouse wheel icon unicode character. + /// MouseWheel = 0xE054, + + /// + /// The mouse with a 1 icon unicode character. + /// Mouse1 = 0xE055, + + /// + /// The mouse with a 2 icon unicode character. + /// Mouse2 = 0xE056, + + /// + /// The mouse with a 3 icon unicode character. + /// Mouse3 = 0xE057, + + /// + /// The mouse with a 4 icon unicode character. + /// Mouse4 = 0xE058, + + /// + /// The mouse with a 5 icon unicode character. + /// Mouse5 = 0xE059, + /// + /// The level English icon unicode character. + /// LevelEn = 0xE06A, + + /// + /// The level German icon unicode character. + /// LevelDe = 0xE06B, + + /// + /// The level French icon unicode character. + /// LevelFr = 0xE06C, + /// + /// The experience icon unicode character. + /// Experience = 0xE0BC, + + /// + /// The experience filled icon unicode character. + /// ExperienceFilled = 0xE0BD, + /// + /// The A.M. time icon unicode character. + /// TimeAm = 0xE06D, + + /// + /// The P.M. time icon unicode character. + /// TimePm = 0xE06E, + /// + /// The right arrow icon unicode character. + /// ArrowRight = 0xE06F, + + /// + /// The down arrow icon unicode character. + /// ArrowDown = 0xE035, + /// + /// The number 0 icon unicode character. + /// Number0 = 0xE060, + + /// + /// The number 1 icon unicode character. + /// Number1 = 0xE061, + + /// + /// The number 2 icon unicode character. + /// Number2 = 0xE062, + + /// + /// The number 3 icon unicode character. + /// Number3 = 0xE063, + + /// + /// The number 4 icon unicode character. + /// Number4 = 0xE064, + + /// + /// The number 5 icon unicode character. + /// Number5 = 0xE065, + + /// + /// The number 6 icon unicode character. + /// Number6 = 0xE066, + + /// + /// The number 7 icon unicode character. + /// Number7 = 0xE067, + + /// + /// The number 8 icon unicode character. + /// Number8 = 0xE068, + + /// + /// The number 9 icon unicode character. + /// Number9 = 0xE069, + /// + /// The boxed number 0 icon unicode character. + /// BoxedNumber0 = 0xE08F, + + /// + /// The boxed number 1 icon unicode character. + /// BoxedNumber1 = 0xE090, + + /// + /// The boxed number 2 icon unicode character. + /// BoxedNumber2 = 0xE091, + + /// + /// The boxed number 3 icon unicode character. + /// BoxedNumber3 = 0xE092, + + /// + /// The boxed number 4 icon unicode character. + /// BoxedNumber4 = 0xE093, + + /// + /// The boxed number 5 icon unicode character. + /// BoxedNumber5 = 0xE094, + + /// + /// The boxed number 6 icon unicode character. + /// BoxedNumber6 = 0xE095, + + /// + /// The boxed number 7 icon unicode character. + /// BoxedNumber7 = 0xE096, + + /// + /// The boxed number 8 icon unicode character. + /// BoxedNumber8 = 0xE097, + + /// + /// The boxed number 9 icon unicode character. + /// BoxedNumber9 = 0xE098, + + /// + /// The boxed number 10 icon unicode character. + /// BoxedNumber10 = 0xE099, + + /// + /// The boxed number 11 icon unicode character. + /// BoxedNumber11 = 0xE09A, + + /// + /// The boxed number 12 icon unicode character. + /// BoxedNumber12 = 0xE09B, + + /// + /// The boxed number 13 icon unicode character. + /// BoxedNumber13 = 0xE09C, + + /// + /// The boxed number 14 icon unicode character. + /// BoxedNumber14 = 0xE09D, + + /// + /// The boxed number 15 icon unicode character. + /// BoxedNumber15 = 0xE09E, + + /// + /// The boxed number 16 icon unicode character. + /// BoxedNumber16 = 0xE09F, + + /// + /// The boxed number 17 icon unicode character. + /// BoxedNumber17 = 0xE0A0, + + /// + /// The boxed number 18 icon unicode character. + /// BoxedNumber18 = 0xE0A1, + + /// + /// The boxed number 19 icon unicode character. + /// BoxedNumber19 = 0xE0A2, + + /// + /// The boxed number 20 icon unicode character. + /// BoxedNumber20 = 0xE0A3, + + /// + /// The boxed number 21 icon unicode character. + /// BoxedNumber21 = 0xE0A4, + + /// + /// The boxed number 22 icon unicode character. + /// BoxedNumber22 = 0xE0A5, + + /// + /// The boxed number 23 icon unicode character. + /// BoxedNumber23 = 0xE0A6, + + /// + /// The boxed number 24 icon unicode character. + /// BoxedNumber24 = 0xE0A7, + + /// + /// The boxed number 25 icon unicode character. + /// BoxedNumber25 = 0xE0A8, + + /// + /// The boxed number 26 icon unicode character. + /// BoxedNumber26 = 0xE0A9, + + /// + /// The boxed number 27 icon unicode character. + /// BoxedNumber27 = 0xE0AA, + + /// + /// The boxed number 28 icon unicode character. + /// BoxedNumber28 = 0xE0AB, + + /// + /// The boxed number 29 icon unicode character. + /// BoxedNumber29 = 0xE0AC, + + /// + /// The boxed number 30 icon unicode character. + /// BoxedNumber30 = 0xE0AD, + + /// + /// The boxed number 31 icon unicode character. + /// BoxedNumber31 = 0xE0AE, + /// + /// The boxed plus icon unicode character. + /// BoxedPlus = 0xE0AF, + + /// + /// The bosed question mark icon unicode character. + /// BoxedQuestionMark = 0xE070, + + /// + /// The boxed star icon unicode character. + /// BoxedStar = 0xE0C0, + /// + /// The boxed Roman numeral 1 (I) icon unicode character. + /// BoxedRoman1 = 0xE0C1, + + /// + /// The boxed Roman numeral 2 (II) icon unicode character. + /// BoxedRoman2 = 0xE0C2, + + /// + /// The boxed Roman numeral 3 (III) icon unicode character. + /// BoxedRoman3 = 0xE0C3, + + /// + /// The boxed Roman numeral 4 (IV) icon unicode character. + /// BoxedRoman4 = 0xE0C4, + + /// + /// The boxed Roman numeral 5 (V) icon unicode character. + /// BoxedRoman5 = 0xE0C5, + + /// + /// The boxed Roman numeral 6 (VI) icon unicode character. + /// BoxedRoman6 = 0xE0C6, + /// + /// The boxed letter A icon unicode character. + /// BoxedLetterA = 0xE071, + + /// + /// The boxed letter B icon unicode character. + /// BoxedLetterB = 0xE072, + + /// + /// The boxed letter C icon unicode character. + /// BoxedLetterC = 0xE073, + + /// + /// The boxed letter D icon unicode character. + /// BoxedLetterD = 0xE074, + + /// + /// The boxed letter E icon unicode character. + /// BoxedLetterE = 0xE075, + + /// + /// The boxed letter F icon unicode character. + /// BoxedLetterF = 0xE076, + + /// + /// The boxed letter G icon unicode character. + /// BoxedLetterG = 0xE077, + + /// + /// The boxed letter H icon unicode character. + /// BoxedLetterH = 0xE078, + + /// + /// The boxed letter I icon unicode character. + /// BoxedLetterI = 0xE079, + + /// + /// The boxed letter J icon unicode character. + /// BoxedLetterJ = 0xE07A, + + /// + /// The boxed letter K icon unicode character. + /// BoxedLetterK = 0xE07B, + + /// + /// The boxed letter L icon unicode character. + /// BoxedLetterL = 0xE07C, + + /// + /// The boxed letter M icon unicode character. + /// BoxedLetterM = 0xE07D, + + /// + /// The boxed letter N icon unicode character. + /// BoxedLetterN = 0xE07E, + + /// + /// The boxed letter O icon unicode character. + /// BoxedLetterO = 0xE07F, + + /// + /// The boxed letter P icon unicode character. + /// BoxedLetterP = 0xE080, + + /// + /// The boxed letter Q icon unicode character. + /// BoxedLetterQ = 0xE081, + + /// + /// The boxed letter R icon unicode character. + /// BoxedLetterR = 0xE082, + + /// + /// The boxed letter S icon unicode character. + /// BoxedLetterS = 0xE083, + + /// + /// The boxed letter T icon unicode character. + /// BoxedLetterT = 0xE084, + + /// + /// The boxed letter U icon unicode character. + /// BoxedLetterU = 0xE085, + + /// + /// The boxed letter V icon unicode character. + /// BoxedLetterV = 0xE086, + + /// + /// The boxed letter W icon unicode character. + /// BoxedLetterW = 0xE087, + + /// + /// The boxed letter X icon unicode character. + /// BoxedLetterX = 0xE088, + + /// + /// The boxed letter Y icon unicode character. + /// BoxedLetterY = 0xE089, + + /// + /// The boxed letter Z icon unicode character. + /// BoxedLetterZ = 0xE08A, + /// + /// The circle icon unicode character. + /// Circle = 0xE04A, + + /// + /// The square icon unicode character. + /// Square = 0xE04B, + + /// + /// The cross icon unicode character. + /// Cross = 0xE04C, + + /// + /// The triangle icon unicode character. + /// Triangle = 0xE04D, - Hexagon = 0xE042, + + /// + /// The hexagon icon unicode character. + /// + Hexagon = 0xE042, + + /// + /// The no-circle/prohobited icon unicode character. + /// Prohibited = 0xE043, - + /// + /// The dice icon unicode character. + /// Dice = 0xE03E, + + /// + /// The debuff icon unicode character. + /// Debuff = 0xE05B, + + /// + /// The buff icon unicode character. + /// Buff = 0xE05C, + + /// + /// The cross-world icon unicode character. + /// CrossWorld = 0xE05D, + /// + /// The Eureka level icon unicode character. + /// EurekaLevel = 0xE03A, + /// + /// The link marker icon unicode character. + /// LinkMarker = 0xE0BB, + /// + /// The glamoured icon unicode character. + /// Glamoured = 0xE03B, + + /// + /// The glamoured and dyed icon unicode character. + /// GlamouredDyed = 0xE04E, + /// + /// The synced quest icon unicode character. + /// QuestSync = 0xE0BE, + + /// + /// The repeatable quest icon unicode character. + /// QuestRepeatable = 0xE0BF, + /// + /// The IME hiragana icon unicode character. + /// ImeHiragana = 0xE020, + + /// + /// The IME katakana icon unicode character. + /// ImeKatakana = 0xE021, + + /// + /// The IME alphanumeric icon unicode character. + /// ImeAlphanumeric = 0xE022, + + /// + /// The IME katakana half-width icon unicode character. + /// ImeKatakanaHalfWidth = 0xE023, + + /// + /// The IME alphanumeric half-width icon unicode character. + /// ImeAlphanumericHalfWidth = 0xE024, + /// + /// The instance (1) icon unicode character. + /// Instance1 = 0xE0B1, + + /// + /// The instance (2) icon unicode character. + /// Instance2 = 0xE0B2, + + /// + /// The instance (3) icon unicode character. + /// Instance3 = 0xE0B3, + + /// + /// The instance (4) icon unicode character. + /// Instance4 = 0xE0B4, + + /// + /// The instance (5) icon unicode character. + /// Instance5 = 0xE0B5, + + /// + /// The instance (6) icon unicode character. + /// Instance6 = 0xE0B6, + + /// + /// The instance (7) icon unicode character. + /// Instance7 = 0xE0B7, + + /// + /// The instance (8) icon unicode character. + /// Instance8 = 0xE0B8, + + /// + /// The instance (9) icon unicode character. + /// Instance9 = 0xE0B9, + + /// + /// The instance merged icon unicode character. + /// InstanceMerged = 0xE0BA, + /// + /// The English local time icon unicode character. + /// LocalTimeEn = 0xE0D0, + + /// + /// The English server time icon unicode character. + /// ServerTimeEn = 0xE0D1, + + /// + /// The English Eorzea time icon unicode character. + /// EorzeaTimeEn = 0xE0D2, + + /// + /// The German local time icon unicode character. + /// LocalTimeDe = 0xE0D3, + + /// + /// The German server time icon unicode character. + /// ServerTimeDe = 0xE0D4, + + /// + /// The German Eorzea time icon unicode character. + /// EorzeaTimeDe = 0xE0D5, + + /// + /// The French local time icon unicode character. + /// LocalTimeFr = 0xE0D6, + + /// + /// The French server time icon unicode character. + /// ServerTimeFr = 0xE0D7, + + /// + /// The French Eorzea time icon unicode character. + /// EorzeaTimeFr = 0xE0D8, + + /// + /// The Japanese local time icon unicode character. + /// LocalTimeJa = 0xE0D9, + + /// + /// The Japanese server time icon unicode character. + /// ServerTimeJa = 0xE0DA, + + /// + /// The Japanese Eorzea time icon unicode character. + /// EorzeaTimeJa = 0xE0DB, } } diff --git a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs index ae709b89d..6b82100e2 100644 --- a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs +++ b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs @@ -1,120 +1,438 @@ -#pragma warning disable 1591 +namespace Dalamud.Game.Text.SeStringHandling +{ + /// + /// This class represents special icons that can appear in chat naturally or as IconPayloads. + /// + public enum BitmapFontIcon : uint + { + /// + /// No icon. + /// + None = 0, -namespace Dalamud.Game.Text.SeStringHandling { - public enum BitmapFontIcon : uint { - None, - ControllerDPadUp, - ControllerDPadDown, - ControllerDPadLeft, - ControllerDPadRight, - ControllerDPadUpDown, - ControllerDPadLeftRight, - ControllerDPadAll, + /// + /// The controller D-pad up icon. + /// + ControllerDPadUp = 1, - ControllerButton0, // Xbox B / PS Circle - ControllerButton1, // Xbox A / PS Cross - ControllerButton2, // Xbox X / PS Square - ControllerButton3, // Xbox Y / PS Triangle + /// + /// The controller D-pad down icon. + /// + ControllerDPadDown = 2, - ControllerShoulderLeft, - ControllerShoulderRight, + /// + /// The controller D-pad left icon. + /// + ControllerDPadLeft = 3, - ControllerTriggerLeft, - ControllerTriggerRight, + /// + /// The controller D-pad right icon. + /// + ControllerDPadRight = 4, - ControllerAnalogLeftStickIn, - ControllerAnalogRightStickIn, + /// + /// The controller D-pad up/down icon. + /// + ControllerDPadUpDown = 5, - ControllerStart, - ControllerBack, + /// + /// The controller D-pad left/right icon. + /// + ControllerDPadLeftRight = 6, - ControllerAnalogLeftStick, - ControllerAnalogLeftStickUpDown, - ControllerAnalogLeftStickLeftRight, + /// + /// The controller D-pad all directions icon. + /// + ControllerDPadAll = 7, - ControllerAnalogRightStick, - ControllerAnalogRightStickUpDown, - ControllerAnalogRightStickLeftRight, + /// + /// The controller button 0 icon (Xbox: B, PlayStation: Circle). + /// + ControllerButton0 = 8, + /// + /// The controller button 1 icon (XBox: A, PlayStation: Cross). + /// + ControllerButton1 = 9, + + /// + /// The controller button 2 icon (XBox: X, PlayStation: Square). + /// + ControllerButton2 = 10, + + /// + /// The controller button 3 icon (BBox: Y, PlayStation: Triangle). + /// + ControllerButton3 = 11, + + /// + /// The controller left shoulder button icon. + /// + ControllerShoulderLeft = 12, + + /// + /// The controller right shoulder button icon. + /// + ControllerShoulderRight = 13, + + /// + /// The controller left trigger button icon. + /// + ControllerTriggerLeft = 14, + + /// + /// The controller right trigger button icon. + /// + ControllerTriggerRight = 15, + + /// + /// The controller left analog stick in icon. + /// + ControllerAnalogLeftStickIn = 16, + + /// + /// The controller right analog stick in icon. + /// + ControllerAnalogRightStickIn = 17, + + /// + /// The controller start button icon. + /// + ControllerStart = 18, + + /// + /// The controller back button icon. + /// + ControllerBack = 19, + + /// + /// The controller left analog stick icon. + /// + ControllerAnalogLeftStick = 20, + + /// + /// The controller left analog stick up/down icon. + /// + ControllerAnalogLeftStickUpDown = 21, + + /// + /// The controller left analog stick left/right icon. + /// + ControllerAnalogLeftStickLeftRight = 22, + + /// + /// The controller right analog stick icon. + /// + ControllerAnalogRightStick = 23, + + /// + /// The controller right analog stick up/down icon. + /// + ControllerAnalogRightStickUpDown = 24, + + /// + /// The controller right analog stick left/right icon. + /// + ControllerAnalogRightStickLeftRight = 25, + + /// + /// The La Noscea region icon. + /// LaNoscea = 51, - BlackShroud, - Thanalan, - AutoTranslateBegin, - AutoTranslateEnd, - ElementFire, - ElementIce, - ElementWind, - ElementEarth, - ElementLightning, - ElementWater, - LevelSync, - Warning, - Ishgard, - Aetheryte, - Aethernet, - GoldStar, - SilverStar, + /// + /// The Black Shroud region icon. + /// + BlackShroud = 52, + /// + /// The Thanalan region icon. + /// + Thanalan = 53, + + /// + /// The auto translate begin icon. + /// + AutoTranslateBegin = 54, + + /// + /// The auto translate end icon. + /// + AutoTranslateEnd = 55, + + /// + /// The fire element icon. + /// + ElementFire = 56, + + /// + /// The ice element icon. + /// + ElementIce = 57, + + /// + /// The wind element icon. + /// + ElementWind = 58, + + /// + /// The earth element icon. + /// + ElementEarth = 59, + + /// + /// The lightning element icon. + /// + ElementLightning = 60, + + /// + /// The water element icon. + /// + ElementWater = 61, + + /// + /// The level sync icon. + /// + LevelSync = 62, + + /// + /// The warning icon. + /// + Warning = 63, + + /// + /// The Ishgard region icon. + /// + Ishgard = 64, + + /// + /// The Aetheryte icon. + /// + Aetheryte = 65, + + /// + /// The Aethernet icon. + /// + Aethernet = 66, + + /// + /// The gold star icon. + /// + GoldStar = 67, + + /// + /// The silver star icon. + /// + SilverStar = 68, + + /// + /// The green dot icon. + /// GreenDot = 70, - SwordUnsheathed, - SwordSheathed, - Dice, + /// + /// The unsheathed sword icon. + /// + SwordUnsheathed = 71, - FlyZone, - FlyZoneLocked, + /// + /// The sheathed sword icon. + /// + SwordSheathed = 72, - NoCircle, + /// + /// The dice icon. + /// + Dice = 73, - NewAdventurer, - Mentor, - MentorPvE, - MentorCrafting, - MentorPvP, + /// + /// The flyable zone icon. + /// + FlyZone = 74, - Tank, - Healer, - DPS, - Crafter, - Gatherer, - AnyClass, + /// + /// The no-flying zone icon. + /// + FlyZoneLocked = 75, - CrossWorld, + /// + /// The no-circle/prohibited icon. + /// + NoCircle = 76, - FateSlay, - FateBoss, - FateGather, - FateDefend, - FateEscort, - FateSpecial1, + /// + /// The sprout icon. + /// + NewAdventurer = 77, - Returner, + /// + /// The mentor icon. + /// + Mentor = 78, - FarEast, - GyrAbania, + /// + /// The PvE mentor icon. + /// + MentorPvE = 79, - FateSpecial2, + /// + /// The crafting mentor icon. + /// + MentorCrafting = 80, - PriorityWorld, + /// + /// The PvP mentor icon. + /// + MentorPvP = 81, - ElementalLevel, - ExclamationRectangle, + /// + /// The tank role icon. + /// + Tank = 82, - NotoriousMonster, + /// + /// The healer role icon. + /// + Healer = 83, - Recording, - Alarm, - - ArrowUp, - ArrowDown, - Crystarium, - - MentorProblem, + /// + /// The DPS role icon. + /// + DPS = 84, - FateUnknownGold, + /// + /// The crafter role icon. + /// + Crafter = 85, - OrangeDiamond, - FateCrafting + /// + /// The gatherer role icon. + /// + Gatherer = 86, + + /// + /// The "any" role icon. + /// + AnyClass = 87, + + /// + /// The cross-world icon. + /// + CrossWorld = 88, + + /// + /// The slay type Fate icon. + /// + FateSlay = 89, + + /// + /// The boss type Fate icon. + /// + FateBoss = 90, + + /// + /// The gather type Fate icon. + /// + FateGather = 91, + + /// + /// The defend type Fate icon. + /// + FateDefend = 92, + + /// + /// The escort type Fate icon. + /// + FateEscort = 93, + + /// + /// The special type 1 Fate icon. + /// + FateSpecial1 = 94, + + /// + /// The returner icon. + /// + Returner = 95, + + /// + /// The Far-East region icon. + /// + FarEast = 96, + + /// + /// The Gyr Albania region icon. + /// + GyrAbania = 97, + + /// + /// The special type 2 Fate icon. + /// + FateSpecial2 = 98, + + /// + /// The priority world icon. + /// + PriorityWorld = 99, + + /// + /// The elemental level icon. + /// + ElementalLevel = 100, + + /// + /// The exclamation rectangle icon. + /// + ExclamationRectangle = 101, + + /// + /// The notorious monster icon. + /// + NotoriousMonster = 102, + + /// + /// The recording icon. + /// + Recording = 103, + + /// + /// The alarm icon. + /// + Alarm = 104, + + /// + /// The arrow up icon. + /// + ArrowUp = 105, + + /// + /// The arrow down icon. + /// + ArrowDown = 106, + + /// + /// The Crystarium region icon. + /// + Crystarium = 107, + + /// + /// The mentor problem icon. + /// + MentorProblem = 108, + + /// + /// The unknown gold type Fate icon. + /// + FateUnknownGold = 109, + + /// + /// The orange diamond icon. + /// + OrangeDiamond = 110, + + /// + /// The crafting type Fate icon. + /// + FateCrafting = 111, } } diff --git a/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs b/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs index d78543698..5bcb2deb0 100644 --- a/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs +++ b/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs @@ -1,9 +1,13 @@ -using System; - namespace Dalamud.Game.Text.SeStringHandling { + /// + /// An interface binding for a payload that can provide readable Text. + /// public interface ITextProvider { + /// + /// Gets the readable text. + /// string Text { get; } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index 81cfd2cee..5d0132815 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; + using Dalamud.Data; using Dalamud.Game.Text.SeStringHandling.Payloads; using Serilog; @@ -19,35 +19,8 @@ namespace Dalamud.Game.Text.SeStringHandling /// /// This class represents a parsed SeString payload. /// - public abstract class Payload + public abstract partial class Payload { - /// - /// The type of this payload. - /// - public abstract PayloadType Type { get; } - - /// - /// Whether this payload has been modified since the last Encode(). - /// - public bool Dirty { get; protected set; } = true; - - /// - /// Encodes the internal state of this payload into a byte[] suitable for sending to in-game - /// handlers such as the chat log. - /// - /// Encoded binary payload data suitable for use with in-game handlers. - protected abstract byte[] EncodeImpl(); - - // TODO: endOfStream is somewhat legacy now that payload length is always handled correctly. - // This could be changed to just take a straight byte[], but that would complicate reading - // but we could probably at least remove the end param - /// - /// Decodes a byte stream from the game into a payload object. - /// - /// A BinaryReader containing at least all the data for this payload. - /// The location holding the end of the data for this payload. - protected abstract void DecodeImpl(BinaryReader reader, long endOfStream); - /// /// The Lumina instance to use for any necessary data lookups. /// @@ -58,31 +31,26 @@ namespace Dalamud.Game.Text.SeStringHandling private byte[] encodedData; /// - /// Encode this payload object into a byte[] useable in-game for things like the chat log. + /// Gets the type of this payload. /// - /// If true, ignores any cached value and forcibly reencodes the payload from its internal representation. - /// A byte[] suitable for use with in-game handlers such as the chat log. - public byte[] Encode(bool force = false) - { - if (Dirty || force) - { - this.encodedData = EncodeImpl(); - Dirty = false; - } + public abstract PayloadType Type { get; } - return this.encodedData; - } + /// + /// Gets or sets a value indicating whether whether this payload has been modified since the last Encode(). + /// + public bool Dirty { get; protected set; } = true; /// /// Decodes a binary representation of a payload into its corresponding nice object payload. /// /// A reader positioned at the start of the payload, and containing at least one entire payload. + /// The DataManager instance. /// The constructed Payload-derived object that was decoded from the binary data. public static Payload Decode(BinaryReader reader, DataManager data) { var payloadStartPos = reader.BaseStream.Position; - Payload payload = null; + Payload payload; var initialByte = reader.ReadByte(); reader.BaseStream.Position--; @@ -113,6 +81,39 @@ namespace Dalamud.Game.Text.SeStringHandling return payload; } + /// + /// Encode this payload object into a byte[] useable in-game for things like the chat log. + /// + /// If true, ignores any cached value and forcibly reencodes the payload from its internal representation. + /// A byte[] suitable for use with in-game handlers such as the chat log. + public byte[] Encode(bool force = false) + { + if (this.Dirty || force) + { + this.encodedData = this.EncodeImpl(); + this.Dirty = false; + } + + return this.encodedData; + } + + /// + /// Encodes the internal state of this payload into a byte[] suitable for sending to in-game + /// handlers such as the chat log. + /// + /// Encoded binary payload data suitable for use with in-game handlers. + protected abstract byte[] EncodeImpl(); + + /// + /// Decodes a byte stream from the game into a payload object. + /// + /// A BinaryReader containing at least all the data for this payload. + /// The location holding the end of the data for this payload. + // TODO: endOfStream is somewhat legacy now that payload length is always handled correctly. + // This could be changed to just take a straight byte[], but that would complicate reading + // but we could probably at least remove the end param + protected abstract void DecodeImpl(BinaryReader reader, long endOfStream); + private static Payload DecodeChunk(BinaryReader reader) { Payload payload = null; @@ -164,18 +165,20 @@ namespace Dalamud.Game.Text.SeStringHandling break; case EmbeddedInfoType.LinkTerminator: - // this has no custom handling and so needs to fallthrough to ensure it is captured + // this has no custom handling and so needs to fallthrough to ensure it is captured default: // but I'm also tired of this log if (subType != EmbeddedInfoType.LinkTerminator) { Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType); } + // rewind so we capture the Interactable byte in the raw data reader.BaseStream.Seek(-1, SeekOrigin.Current); break; } } + break; case SeStringChunkType.AutoTranslateKey: @@ -216,43 +219,126 @@ namespace Dalamud.Game.Text.SeStringHandling return payload; } + } - #region parse constants and helpers - + /// + /// Parsing helpers. + /// + public abstract partial class Payload + { + /// + /// The start byte of a payload. + /// protected const byte START_BYTE = 0x02; + + /// + /// The end byte of a payload. + /// protected const byte END_BYTE = 0x03; - protected enum SeStringChunkType - { - Icon = 0x12, - EmphasisItalic = 0x1A, - SeHyphen = 0x1F, - Interactable = 0x27, - AutoTranslateKey = 0x2E, - UIForeground = 0x48, - UIGlow = 0x49 - } - + /// + /// This represents the type of embedded info in a payload. + /// public enum EmbeddedInfoType { + /// + /// A player's name. + /// PlayerName = 0x01, + + /// + /// The link to an iteme. + /// ItemLink = 0x03, + + /// + /// The link to a map position. + /// MapPositionLink = 0x04, + + /// + /// The link to a quest. + /// QuestLink = 0x05, + + /// + /// A status effect. + /// Status = 0x09, - DalamudLink = 0x0F, // Dalamud Custom + /// + /// A custom Dalamud link. + /// + DalamudLink = 0x0F, - LinkTerminator = 0xCF // not clear but seems to always follow a link + /// + /// A link terminator. + /// + /// + /// It is not exactly clear what this is, but seems to always follow a link. + /// + LinkTerminator = 0xCF, } + /// + /// This represents the type of payload and how it should be encoded. + /// + protected enum SeStringChunkType + { + /// + /// See the class. + /// + Icon = 0x12, + /// + /// See the class. + /// + EmphasisItalic = 0x1A, + + /// + /// See the class. + /// + SeHyphen = 0x1F, + + /// + /// See any of the link-type classes: + /// , + /// , + /// , + /// , + /// , + /// . + /// + Interactable = 0x27, + + /// + /// See the class. + /// + AutoTranslateKey = 0x2E, + + /// + /// See the class. + /// + UIForeground = 0x48, + + /// + /// See the class. + /// + UIGlow = 0x49, + } + + /// + /// Retrieve the packed integer from SE's native data format. + /// + /// The BinaryReader instance. + /// An integer. // made protected, unless we actually want to use it externally // in which case it should probably go live somewhere else protected static uint GetInteger(BinaryReader input) { uint marker = input.ReadByte(); - if (marker < 0xD0) return marker - 1; + if (marker < 0xD0) + return marker - 1; // the game adds 0xF0 marker for values >= 0xCF // uasge of 0xD0-0xEF is unknown, should we throw here? @@ -269,6 +355,11 @@ namespace Dalamud.Game.Text.SeStringHandling return BitConverter.ToUInt32(ret, 0); } + /// + /// Create a packed integer in Se's native data format. + /// + /// The value to pack. + /// A packed integer. protected static byte[] MakeInteger(uint value) { if (value < 0xCF) @@ -287,22 +378,33 @@ namespace Dalamud.Game.Text.SeStringHandling ret[0] |= (byte)(1 << i); } } + ret[0] -= 1; return ret.ToArray(); } - protected static (uint, uint) GetPackedIntegers(BinaryReader input) + /// + /// From a binary packed integer, get the high and low bytes. + /// + /// The BinaryReader instance. + /// The high and low bytes. + protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input) { var value = GetInteger(input); return (value >> 16, value & 0xFFFF); } - protected static byte[] MakePackedInteger(uint val1, uint val2) + /// + /// Create a packed integer from the given high and low bytes. + /// + /// The high order bytes. + /// The low order bytes. + /// A packed integer. + protected static byte[] MakePackedInteger(uint high, uint low) { - var value = (val1 << 16) | (val2 & 0xFFFF); + var value = (high << 16) | (low & 0xFFFF); return MakeInteger(value); } - #endregion } } diff --git a/Dalamud/Game/Text/SeStringHandling/PayloadType.cs b/Dalamud/Game/Text/SeStringHandling/PayloadType.cs index 5ca36d138..8e6c5ffbd 100644 --- a/Dalamud/Game/Text/SeStringHandling/PayloadType.cs +++ b/Dalamud/Game/Text/SeStringHandling/PayloadType.cs @@ -1,4 +1,3 @@ - namespace Dalamud.Game.Text.SeStringHandling { /// @@ -10,54 +9,70 @@ namespace Dalamud.Game.Text.SeStringHandling /// An SeString payload representing a player link. /// Player, + /// /// An SeString payload representing an Item link. /// Item, + /// /// An SeString payload representing an Status Effect link. /// Status, + /// /// An SeString payload representing raw, typed text. /// RawText, + /// /// An SeString payload representing a text foreground color. /// UIForeground, + /// /// An SeString payload representing a text glow color. /// UIGlow, + /// /// An SeString payload representing a map position link, such as from <flag> or <pos>. /// MapLink, + /// /// An SeString payload representing an auto-translate dictionary entry. /// AutoTranslateText, + /// /// An SeString payload representing italic emphasis formatting on text. /// EmphasisItalic, + /// /// An SeString payload representing a bitmap icon. /// Icon, + /// /// A SeString payload representing a quest link. /// Quest, + /// - /// A SeString payload representing a custom clickable link for dalamud plugins + /// A SeString payload representing a custom clickable link for dalamud plugins. /// DalamudLink, + /// /// An SeString payload representing any data we don't handle. /// Unknown, + + /// + /// An SeString payload representing a doublewide SE hypen. + /// SeHyphen, } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index fef82940c..24e2a139f 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -1,11 +1,12 @@ -using Lumina.Excel.GeneratedSheets; -using Serilog; using System; using System.Collections.Generic; using System.IO; using System.Linq; + using Dalamud.Data; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; +using Serilog; namespace Dalamud.Game.Text.SeStringHandling.Payloads { @@ -14,25 +15,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class AutoTranslatePayload : Payload, ITextProvider { - public override PayloadType Type => PayloadType.AutoTranslateText; - private string text; - /// - /// The actual text displayed in-game for this payload. - /// - /// - /// Value is evaluated lazily and cached. - /// - public string Text - { - get - { - // wrap the text in the colored brackets that is uses in-game, since those - // are not actually part of any of the payloads - this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {Resolve()} {(char)SeIconChar.AutoTranslateClose}"; - return this.text; - } - } [JsonProperty] private uint group; @@ -40,9 +23,8 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads [JsonProperty] private uint key; - internal AutoTranslatePayload() { } - /// + /// Initializes a new instance of the class. /// Creates a new auto-translate payload. /// /// DataManager instance needed to resolve game data. @@ -52,19 +34,49 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// This table is somewhat complicated in structure, and so using this constructor may not be very nice. /// There is probably little use to create one of these, however. /// - public AutoTranslatePayload(DataManager data, uint group, uint key) { + public AutoTranslatePayload(DataManager data, uint group, uint key) + { + // TODO: friendlier ctor? not sure how to handle that given how weird the tables are this.DataResolver = data; this.group = group; this.key = key; } - // TODO: friendlier ctor? not sure how to handle that given how weird the tables are - - public override string ToString() + /// + /// Initializes a new instance of the class. + /// + internal AutoTranslatePayload() { - return $"{Type} - Group: {group}, Key: {key}, Text: {Text}"; } + /// + public override PayloadType Type => PayloadType.AutoTranslateText; + + /// + /// Gets the actual text displayed in-game for this payload. + /// + /// + /// Value is evaluated lazily and cached. + /// + public string Text + { + get + { + // wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads + return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}"; + } + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}"; + } + + /// protected override byte[] EncodeImpl() { var keyBytes = MakeInteger(this.key); @@ -74,7 +86,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads { START_BYTE, (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, - (byte)this.group + (byte)this.group, }; bytes.AddRange(keyBytes); bytes.Add(END_BYTE); @@ -82,6 +94,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { // this seems to always be a bare byte, and not following normal integer encoding @@ -105,7 +118,9 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads // (again, if it's meant for another table) row = sheet.GetRow(this.key); } - catch { } // don't care, row will be null + catch + { + } // don't care, row will be null if (row?.Group == this.group) { @@ -142,7 +157,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads "TextCommand" => this.DataResolver.GetExcelSheet().GetRow(this.key).Command, "Tribe" => this.DataResolver.GetExcelSheet().GetRow(this.key).Masculine, "Weather" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - _ => throw new Exception(actualTableName) + _ => throw new Exception(actualTableName), }; value = name; diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index 032915a58..0c18243d4 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -1,48 +1,62 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; -using System.Threading.Tasks; + using JetBrains.Annotations; -namespace Dalamud.Game.Text.SeStringHandling.Payloads { - +namespace Dalamud.Game.Text.SeStringHandling.Payloads +{ /// - /// + /// This class represents a custom Dalamud clickable chat link. /// - public class DalamudLinkPayload : Payload { + public class DalamudLinkPayload : Payload + { + /// public override PayloadType Type => PayloadType.DalamudLink; + /// + /// Gets the plugin command ID to be linked. + /// public uint CommandId { get; internal set; } = 0; + /// + /// Gets the plugin name to be linked. + /// [NotNull] public string Plugin { get; internal set; } = string.Empty; - - protected override byte[] EncodeImpl() { - var pluginBytes = Encoding.UTF8.GetBytes(Plugin); - var commandBytes = MakeInteger(CommandId); + + /// + public override string ToString() + { + return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}"; + } + + /// + protected override byte[] EncodeImpl() + { + var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin); + var commandBytes = MakeInteger(this.CommandId); var chunkLen = 3 + pluginBytes.Length + commandBytes.Length; - if (chunkLen > 255) { + if (chunkLen > 255) + { throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload"); } - var bytes = new List {START_BYTE, (byte) SeStringChunkType.Interactable, (byte) chunkLen, (byte) EmbeddedInfoType.DalamudLink}; - bytes.Add((byte) pluginBytes.Length); + var bytes = new List { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink }; + bytes.Add((byte)pluginBytes.Length); bytes.AddRange(pluginBytes); bytes.AddRange(commandBytes); bytes.Add(END_BYTE); return bytes.ToArray(); } - protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte())); - CommandId = GetInteger(reader); - } - - public override string ToString() { - return $"{Type} - Plugin: {Plugin}, Command: {CommandId}"; + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte())); + this.CommandId = GetInteger(reader); } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs index 4b08e5ed8..b6c3bbd76 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs @@ -14,47 +14,58 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads public class EmphasisItalicPayload : Payload { /// - /// Payload representing enabling italics on following text. - /// - public static EmphasisItalicPayload ItalicsOn => new EmphasisItalicPayload(true); - /// - /// Payload representing disabling italics on following text. - /// - public static EmphasisItalicPayload ItalicsOff => new EmphasisItalicPayload(false); - - public override PayloadType Type => PayloadType.EmphasisItalic; - - /// - /// Whether this payload enables italics formatting for following text. - /// - public bool IsEnabled { get; private set; } - - internal EmphasisItalicPayload() { } - - /// + /// Initializes a new instance of the class. /// Creates an EmphasisItalicPayload. /// /// Whether italics formatting should be enabled or disabled for following text. public EmphasisItalicPayload(bool enabled) { - IsEnabled = enabled; + this.IsEnabled = enabled; } + /// + /// Initializes a new instance of the class. + /// Creates an EmphasisItalicPayload. + /// + internal EmphasisItalicPayload() + { + } + + /// + /// Gets a payload representing enabling italics on following text. + /// + public static EmphasisItalicPayload ItalicsOn => new(true); + + /// + /// Gets a payload representing disabling italics on following text. + /// + public static EmphasisItalicPayload ItalicsOff => new(false); + + /// + /// Gets a value indicating whether this payload enables italics formatting for following text. + /// + public bool IsEnabled { get; private set; } + + /// + public override PayloadType Type => PayloadType.EmphasisItalic; + + /// public override string ToString() { - return $"{Type} - Enabled: {IsEnabled}"; + return $"{this.Type} - Enabled: {this.IsEnabled}"; } + /// protected override byte[] EncodeImpl() { // realistically this will always be a single byte of value 1 or 2 // but we'll treat it normally anyway - var enabledBytes = MakeInteger(IsEnabled ? (uint)1 : 0); + var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0); var chunkLen = enabledBytes.Length + 1; var bytes = new List() { - START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen + START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen, }; bytes.AddRange(enabledBytes); bytes.Add(END_BYTE); @@ -62,9 +73,10 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - IsEnabled = (GetInteger(reader) == 1); + this.IsEnabled = GetInteger(reader) == 1; } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs index 69b3dc97e..e526d9c70 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs @@ -1,51 +1,71 @@ +using System; using System.Collections.Generic; using System.IO; -using System; - -namespace Dalamud.Game.Text.SeStringHandling.Payloads { +namespace Dalamud.Game.Text.SeStringHandling.Payloads +{ /// - /// SeString payload representing a bitmap icon from fontIcon + /// SeString payload representing a bitmap icon from fontIcon. /// - public class IconPayload : Payload { + public class IconPayload : Payload + { + /// + /// Initializes a new instance of the class. + /// Create a Icon payload for the specified icon. + /// + /// The Icon. + public IconPayload(BitmapFontIcon icon) + { + this.Icon = icon; + } /// - /// Index of the icon + /// Initializes a new instance of the class. + /// Create a Icon payload for the specified icon. + /// + /// Index of the icon. + [Obsolete("IconPayload(uint) is deprecated, please use IconPayload(BitmapFontIcon).")] + public IconPayload(uint iconIndex) + : this((BitmapFontIcon)iconIndex) + { + } + + /// + /// Initializes a new instance of the class. + /// Create a Icon payload for the specified icon. + /// + internal IconPayload() + { + } + + /// + public override PayloadType Type => PayloadType.Icon; + + /// + /// Gets the index of the icon. /// [Obsolete("Use IconPayload.Icon")] - public uint IconIndex => (uint) Icon; + public uint IconIndex => (uint)this.Icon; /// - /// Icon the payload represents. + /// Gets or sets the icon the payload represents. /// public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None; - internal IconPayload() { } - - /// - /// Create a Icon payload for the specified icon. - /// - /// Index of the icon - [Obsolete("IconPayload(uint) is deprecated, please use IconPayload(BitmapFontIcon).")] - public IconPayload(uint iconIndex) : this((BitmapFontIcon) iconIndex) { } - - /// - /// Create a Icon payload for the specified icon. - /// - /// The Icon - public IconPayload(BitmapFontIcon icon) { - Icon = icon; + /// + public override string ToString() + { + return $"{this.Type} - {this.Icon}"; } /// - public override PayloadType Type => PayloadType.Icon; - - /// - protected override byte[] EncodeImpl() { - var indexBytes = MakeInteger((uint) this.Icon); + protected override byte[] EncodeImpl() + { + var indexBytes = MakeInteger((uint)this.Icon); var chunkLen = indexBytes.Length + 1; - var bytes = new List(new byte[] { - START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen + var bytes = new List(new byte[] + { + START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen, }); bytes.AddRange(indexBytes); bytes.Add(END_BYTE); @@ -53,14 +73,9 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads { } /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - Icon = (BitmapFontIcon) GetInteger(reader); + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.Icon = (BitmapFontIcon)GetInteger(reader); } - - /// - public override string ToString() { - return $"{Type} - {Icon}"; - } - } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs index 376c40afd..6f40a0368 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -1,8 +1,8 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; + using Dalamud.Data; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; @@ -14,32 +14,48 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class ItemPayload : Payload { - public override PayloadType Type => PayloadType.Item; - private Item item; - /// - /// The underlying Lumina Item represented by this payload. - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public Item Item - { - get - { - this.item ??= this.DataResolver.GetExcelSheet().GetRow(this.itemId); - return this.item; - } - } // mainly to allow overriding the name (for things like owo) // TODO: even though this is present in some item links, it may not really have a use at all // For things like owo, changing the text payload is probably correct, whereas changing the // actual embedded name might not work properly. private string displayName = null; + + [JsonProperty] + private uint itemId; + /// - /// The displayed name for this item link. Note that incoming links only sometimes have names embedded, + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + /// DataManager instance needed to resolve game data. + /// The id of the item. + /// Whether or not the link should be for the high-quality variant of the item. + /// An optional name to include in the item link. Typically this should + /// be left as null, or set to the normal item name. Actual overrides are better done with the subsequent + /// TextPayload that is a part of a full item link in chat. + public ItemPayload(DataManager data, uint itemId, bool isHQ, string displayNameOverride = null) + { + this.DataResolver = data; + this.itemId = itemId; + this.IsHQ = isHQ; + this.displayName = displayNameOverride; + } + + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + internal ItemPayload() + { + } + + /// + public override PayloadType Type => PayloadType.Item; + + /// + /// Gets or sets the displayed name for this item link. Note that incoming links only sometimes have names embedded, /// often the name is only present in a following text payload. /// public string DisplayName @@ -52,54 +68,44 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads set { this.displayName = value; - Dirty = true; + this.Dirty = true; } } /// - /// Whether or not this item link is for a high-quality version of the item. + /// Gets the underlying Lumina Item represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Item Item => this.item ??= this.DataResolver.GetExcelSheet().GetRow(this.itemId); + + /// + /// Gets a value indicating whether or not this item link is for a high-quality version of the item. /// [JsonProperty] public bool IsHQ { get; private set; } = false; - [JsonProperty] - private uint itemId; - - internal ItemPayload() { } - - /// - /// Creates a payload representing an interactable item link for the specified item. - /// - /// DataManager instance needed to resolve game data. - /// The id of the item. - /// Whether or not the link should be for the high-quality variant of the item. - /// An optional name to include in the item link. Typically this should - /// be left as null, or set to the normal item name. Actual overrides are better done with the subsequent - /// TextPayload that is a part of a full item link in chat. - public ItemPayload(DataManager data, uint itemId, bool isHQ, string displayNameOverride = null) { - this.DataResolver = data; - this.itemId = itemId; - this.IsHQ = isHQ; - this.displayName = displayNameOverride; - } - + /// public override string ToString() { - return $"{Type} - ItemId: {itemId}, IsHQ: {IsHQ}, Name: {this.displayName ?? Item.Name}"; + return $"{this.Type} - ItemId: {this.itemId}, IsHQ: {this.IsHQ}, Name: {this.displayName ?? this.Item.Name}"; } + /// protected override byte[] EncodeImpl() { - var actualItemId = IsHQ ? this.itemId + 1000000 : this.itemId; + var actualItemId = this.IsHQ ? this.itemId + 1000000 : this.itemId; var idBytes = MakeInteger(actualItemId); - bool hasName = !string.IsNullOrEmpty(this.displayName); + var hasName = !string.IsNullOrEmpty(this.displayName); var chunkLen = idBytes.Length + 4; if (hasName) { // 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself - chunkLen += (1 + 1 + this.displayName.Length); - if (IsHQ) + chunkLen += 1 + 1 + this.displayName.Length; + if (this.IsHQ) { chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space } @@ -108,7 +114,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads var bytes = new List() { START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink + (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink, }; bytes.AddRange(idBytes); // unk @@ -118,7 +124,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads if (hasName) { var nameLen = this.displayName.Length + 1; - if (IsHQ) + if (this.IsHQ) { nameLen += 4; // space plus 3 bytes for HQ symbol } @@ -126,11 +132,11 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads bytes.AddRange(new byte[] { 0xFF, // unk - (byte)nameLen + (byte)nameLen, }); bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName)); - if (IsHQ) + if (this.IsHQ) { // space and HQ symbol bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC }); @@ -142,6 +148,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { this.itemId = GetInteger(reader); @@ -149,7 +156,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads if (this.itemId > 1000000) { this.itemId -= 1000000; - IsHQ = true; + this.IsHQ = true; } if (reader.BaseStream.Position + 3 < endOfStream) @@ -167,7 +174,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads // HQ items have the HQ symbol as part of the name, but since we already recorded // the HQ flag, we want just the bare name - if (IsHQ) + if (this.IsHQ) { itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray(); } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs index e6e07f422..fbcb447c7 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs @@ -1,8 +1,9 @@ -using Lumina.Excel.GeneratedSheets; using System; using System.Collections.Generic; using System.IO; + using Dalamud.Data; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads @@ -12,140 +13,19 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class MapLinkPayload : Payload { - public override PayloadType Type => PayloadType.MapLink; - private Map map; - /// - /// The Map specified for this map link. - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public Map Map - { - get - { - this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); - return this.map; - } - } - private TerritoryType territoryType; - /// - /// The TerritoryType specified for this map link. - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public TerritoryType TerritoryType - { - get - { - this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); - return this.territoryType; - } - } - - /// - /// The internal x-coordinate for this map position. - /// - public int RawX { get; private set; } - - /// - /// The internal y-coordinate for this map position. - /// - public int RawY { get; private set; } - - // these could be cached, but this isn't really too egregious - /// - /// The readable x-coordinate position for this map link. This value is approximate and unrounded. - /// - public float XCoord - { - get - { - return ConvertRawPositionToMapCoordinate(RawX, Map.SizeFactor); - } - } - - /// - /// The readable y-coordinate position for this map link. This value is approximate and unrounded. - /// - [JsonIgnore] - public float YCoord - { - get - { - return ConvertRawPositionToMapCoordinate(RawY, Map.SizeFactor); - } - } - - /// - /// The printable map coordinates for this link. This value tries to match the in-game printable text as closely as possible - /// but is an approximation and may be slightly off for some positions. - /// - [JsonIgnore] - public string CoordinateString - { - get - { - // this truncates the values to one decimal without rounding, which is what the game does - // the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues - // TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable - const float fudge = 0.02f; - var x = Math.Truncate((XCoord+fudge) * 10.0f) / 10.0f; - var y = Math.Truncate((YCoord+fudge) * 10.0f) / 10.0f; - - // the formatting and spacing the game uses - return $"( {x.ToString("0.0")} , {y.ToString("0.0")} )"; - } - } - private string placeNameRegion; - /// - /// The region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea" - /// - [JsonIgnore] - public string PlaceNameRegion - { - get - { - this.placeNameRegion ??= TerritoryType.PlaceNameRegion.Value?.Name; - return this.placeNameRegion; - } - } - private string placeName; - /// - /// The place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks" - /// - [JsonIgnore] - public string PlaceName - { - get - { - this.placeName ??= TerritoryType.PlaceName.Value?.Name; - return this.placeName; - } - } - - /// - /// The data string for this map link, for use by internal game functions that take a string variant and not a binary payload. - /// - public string DataString => $"m:{TerritoryType.RowId},{Map.RowId},{RawX},{RawY}"; [JsonProperty] private uint territoryTypeId; [JsonProperty] private uint mapId; - // there is no Z; it's purely in the text payload where applicable - - internal MapLinkPayload() { } /// + /// Initializes a new instance of the class. /// Creates an interactable MapLinkPayload from a human-readable position. /// /// DataManager instance needed to resolve game data. @@ -154,18 +34,20 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// The human-readable x-coordinate for this link. /// The human-readable y-coordinate for this link. /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - public MapLinkPayload(DataManager data, uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) { + public MapLinkPayload(DataManager data, uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) + { this.DataResolver = data; this.territoryTypeId = territoryTypeId; this.mapId = mapId; // this fudge is necessary basically to ensure we don't shift down a full tenth // because essentially values are truncated instead of rounded, so 3.09999f will become // 3.0f and not 3.1f - RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, Map.SizeFactor); - RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, Map.SizeFactor); + this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor); + this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor); } /// + /// Initializes a new instance of the class. /// Creates an interactable MapLinkPayload from a raw position. /// /// DataManager instance needed to resolve game data. @@ -178,27 +60,121 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads this.DataResolver = data; this.territoryTypeId = territoryTypeId; this.mapId = mapId; - RawX = rawX; - RawY = rawY; + this.RawX = rawX; + this.RawY = rawY; } + /// + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a human-readable position. + /// + internal MapLinkPayload() + { + } + + /// + public override PayloadType Type => PayloadType.MapLink; + + /// + /// Gets the Map specified for this map link. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Map Map => this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); + + /// + /// Gets the TerritoryType specified for this map link. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); + + /// + /// Gets the internal x-coordinate for this map position. + /// + public int RawX { get; private set; } + + /// + /// Gets the internal y-coordinate for this map position. + /// + public int RawY { get; private set; } + + // these could be cached, but this isn't really too egregious + + /// + /// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded. + /// + public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor); + + /// + /// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded. + /// + [JsonIgnore] + public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor); + + // there is no Z; it's purely in the text payload where applicable + + /// + /// Gets the printable map coordinates for this link. This value tries to match the in-game printable text as closely + /// as possible but is an approximation and may be slightly off for some positions. + /// + [JsonIgnore] + public string CoordinateString + { + get + { + // this truncates the values to one decimal without rounding, which is what the game does + // the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues + // TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable + const float fudge = 0.02f; + var x = Math.Truncate((this.XCoord + fudge) * 10.0f) / 10.0f; + var y = Math.Truncate((this.YCoord + fudge) * 10.0f) / 10.0f; + + // the formatting and spacing the game uses + return $"( {x:0.0} , {y:0.0} )"; + } + } + + /// + /// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea". + /// + [JsonIgnore] + public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name; + + /// + /// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks". + /// + [JsonIgnore] + public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name; + + /// + /// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload. + /// + public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}"; + + /// public override string ToString() { - return $"{Type} - TerritoryTypeId: {territoryTypeId}, MapId: {mapId}, RawX: {RawX}, RawY: {RawY}, display: {PlaceName} {CoordinateString}"; + return $"{this.Type} - TerritoryTypeId: {this.territoryTypeId}, MapId: {this.mapId}, RawX: {this.RawX}, RawY: {this.RawY}, display: {this.PlaceName} {this.CoordinateString}"; } + /// protected override byte[] EncodeImpl() { var packedTerritoryAndMapBytes = MakePackedInteger(this.territoryTypeId, this.mapId); - var xBytes = MakeInteger(unchecked((uint)RawX)); - var yBytes = MakeInteger(unchecked((uint)RawY)); + var xBytes = MakeInteger(unchecked((uint)this.RawX)); + var yBytes = MakeInteger(unchecked((uint)this.RawY)); var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length; var bytes = new List() { START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink + (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink, }; bytes.AddRange(packedTerritoryAndMapBytes); @@ -211,6 +187,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { // for debugging for now @@ -221,8 +198,8 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads try { (this.territoryTypeId, this.mapId) = GetPackedIntegers(reader); - RawX = unchecked((int)GetInteger(reader)); - RawY = unchecked((int)GetInteger(reader)); + this.RawX = unchecked((int)GetInteger(reader)); + this.RawY = unchecked((int)GetInteger(reader)); // the Z coordinate is never in this chunk, just the text (if applicable) // seems to always be FF 01 @@ -237,6 +214,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads } #region ugliness + // from https://github.com/xivapi/ffxiv-datamining/blob/master/docs/MapCoordinates.md // extra 1/1000 because that is how the network ints are done private float ConvertRawPositionToMapCoordinate(int pos, float scale) @@ -257,6 +235,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return (int)scaledPos; } + #endregion } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs index 261a74f75..484290c9e 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -1,9 +1,9 @@ -using Lumina.Excel.GeneratedSheets; -using System; using System.Collections.Generic; using System.IO; using System.Text; + using Dalamud.Data; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads @@ -13,70 +13,80 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class PlayerPayload : Payload { - public override PayloadType Type => PayloadType.Player; - - [JsonProperty] - private string playerName; - /// - /// The player's displayed name. This does not contain the server name. - /// - [JsonIgnore] - public string PlayerName - { - get { return this.playerName; } - set - { - this.playerName = value; - Dirty = true; - } - } - private World world; - /// - /// The Lumina object representing the player's home server. - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public World World - { - get - { - this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); - return this.world; - } - } - - /// - /// A text representation of this player link matching how it might appear in-game. - /// The world name will always be present. - /// - [JsonIgnore] - public string DisplayedName => $"{PlayerName}{(char)SeIconChar.CrossWorld}{World.Name}"; [JsonProperty] private uint serverId; - internal PlayerPayload() { } + [JsonProperty] + private string playerName; /// + /// Initializes a new instance of the class. /// Create a PlayerPayload link for the specified player. /// /// DataManager instance needed to resolve game data. /// The player's displayed name. /// The player's home server id. - public PlayerPayload(DataManager data, string playerName, uint serverId) { + public PlayerPayload(DataManager data, string playerName, uint serverId) + { this.DataResolver = data; this.playerName = playerName; this.serverId = serverId; } - public override string ToString() + /// + /// Initializes a new instance of the class. + /// Create a PlayerPayload link for the specified player. + /// + internal PlayerPayload() { - return $"{Type} - PlayerName: {PlayerName}, ServerId: {serverId}, ServerName: {World.Name}"; } + /// + /// Gets the Lumina object representing the player's home server. + /// + /// + /// Value is evaluated lazily and cached. + /// + [JsonIgnore] + public World World => this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); + + /// + /// Gets or sets the player's displayed name. This does not contain the server name. + /// + [JsonIgnore] + public string PlayerName + { + get + { + return this.playerName; + } + + set + { + this.playerName = value; + this.Dirty = true; + } + } + + /// + /// Gets the text representation of this player link matching how it might appear in-game. + /// The world name will always be present. + /// + [JsonIgnore] + public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}"; + + /// + public override PayloadType Type => PayloadType.Player; + + /// + public override string ToString() + { + return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}"; + } + + /// protected override byte[] EncodeImpl() { var chunkLen = this.playerName.Length + 7; @@ -85,9 +95,10 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, /* unk */ 0x01, - (byte)(this.serverId+1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually - /* unk */0x01, /* unk */0xFF, // these sometimes vary but are frequently this - (byte)(this.playerName.Length+1) + (byte)(this.serverId + 1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually + /* unk */ 0x01, + /* unk */ 0xFF, // these sometimes vary but are frequently this + (byte)(this.playerName.Length + 1), }; bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); @@ -97,19 +108,20 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads // encoded names are followed by the name in plain text again // use the payload parsing for consistency, as this is technically a new chunk - bytes.AddRange(new TextPayload(playerName).Encode()); + bytes.AddRange(new TextPayload(this.playerName).Encode()); // unsure about this entire packet, but it seems to always follow a name bytes.AddRange(new byte[] { START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator, 0x01, 0x01, 0x01, 0xFF, 0x01, - END_BYTE + END_BYTE, }); return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { // unk diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs index 6b2030047..a250d3b58 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs @@ -1,68 +1,79 @@ -using System; using System.Collections.Generic; using System.IO; + using Dalamud.Data; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads { +namespace Dalamud.Game.Text.SeStringHandling.Payloads +{ /// /// An SeString Payload representing an interactable quest link. /// - public class QuestPayload : Payload { - public override PayloadType Type => PayloadType.Quest; - + public class QuestPayload : Payload + { private Quest quest; - /// - /// The underlying Lumina Quest represented by this payload. - /// - /// - /// Value is evaluated lazily and cached. - /// - - [JsonIgnore] - public Quest Quest { - get { - this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); - return this.quest; - } - } [JsonProperty] private uint questId; - internal QuestPayload() { } - /// + /// Initializes a new instance of the class. /// Creates a payload representing an interactable quest link for the specified quest. /// /// DataManager instance needed to resolve game data. /// The id of the quest. - public QuestPayload(DataManager data, uint questId) { + public QuestPayload(DataManager data, uint questId) + { this.DataResolver = data; this.questId = questId; } - /// - public override string ToString() { - return $"{Type} - QuestId: {this.questId}, Name: {Quest?.Name ?? "QUEST NOT FOUND"}"; + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable quest link for the specified quest. + /// + internal QuestPayload() + { } - protected override byte[] EncodeImpl() { - var idBytes = MakeInteger((ushort) this.questId); + /// + public override PayloadType Type => PayloadType.Quest; + + /// + /// Gets the underlying Lumina Quest represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); + + /// + public override string ToString() + { + return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}"; + } + + /// + protected override byte[] EncodeImpl() + { + var idBytes = MakeInteger((ushort)this.questId); var chunkLen = idBytes.Length + 4; - var bytes = new List() { - START_BYTE, (byte) SeStringChunkType.Interactable, (byte) chunkLen, (byte) EmbeddedInfoType.QuestLink, + var bytes = new List() + { + START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.QuestLink, }; bytes.AddRange(idBytes); - bytes.AddRange(new byte[] {0x01, 0x01, END_BYTE}); + bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE }); return bytes.ToArray(); - } - protected override void DecodeImpl(BinaryReader reader, long endOfStream) { + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { // Game uses int16, Luimina uses int32 this.questId = GetInteger(reader) + 65536; } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs index d672d32c3..ae3754839 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs @@ -1,9 +1,10 @@ -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Newtonsoft.Json; + namespace Dalamud.Game.Text.SeStringHandling.Payloads { /// @@ -13,65 +14,91 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class RawPayload : Payload { - // this and others could be an actual static member somewhere and avoid construction costs, but that probably isn't a real concern - /// - /// A fixed Payload representing a common link-termination sequence, found in many payload chains. - /// - public static RawPayload LinkTerminator => new RawPayload(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 }); - - public override PayloadType Type => PayloadType.Unknown; - - [JsonProperty] - private byte[] data; - // this is a bit different from the underlying data - // We need to store just the chunk data for decode to behave nicely, but when reading data out - // it makes more sense to get the entire payload - /// - /// The entire payload byte sequence for this payload. - /// The returned data is a clone and modifications will not be persisted. - /// - [JsonIgnore] - public byte[] Data - { - get - { - // for now don't allow modifying the contents - // because we don't really have a way to track Dirty - return (byte[])Encode().Clone(); - } - } - [JsonProperty] private byte chunkType; - [JsonConstructor] - internal RawPayload(byte chunkType) - { - this.chunkType = chunkType; - } + [JsonProperty] + private byte[] data; + /// + /// Initializes a new instance of the class. + /// + /// The payload data. public RawPayload(byte[] data) { // this payload is 'special' in that we require the entire chunk to be passed in // and not just the data after the header // This sets data to hold the chunk data fter the header, excluding the END_BYTE this.chunkType = data[1]; - this.data = data.Skip(3).Take(data.Length-4).ToArray(); + this.data = data.Skip(3).Take(data.Length - 4).ToArray(); } - public override string ToString() + /// + /// Initializes a new instance of the class. + /// + /// The chunk type. + [JsonConstructor] + internal RawPayload(byte chunkType) { - return $"{Type} - Data: {BitConverter.ToString(Data).Replace("-", " ")}"; + this.chunkType = chunkType; } - public override bool Equals(object obj) { - if (obj is RawPayload rp) { - if (rp.Data.Length != this.Data.Length) return false; - return !Data.Where((t, i) => rp.Data[i] != t).Any(); + /// + /// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains. + /// + public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 }); + + /// + public override PayloadType Type => PayloadType.Unknown; + + /// + /// Gets the entire payload byte sequence for this payload. + /// The returned data is a clone and modifications will not be persisted. + /// + [JsonIgnore] + public byte[] Data + { + // this is a bit different from the underlying data + // We need to store just the chunk data for decode to behave nicely, but when reading data out + // it makes more sense to get the entire payload + get + { + // for now don't allow modifying the contents + // because we don't really have a way to track Dirty + return (byte[])this.Encode().Clone(); } + } + + /// + public override bool Equals(object obj) + { + if (obj is RawPayload rp) + { + if (rp.Data.Length != this.Data.Length) return false; + return !this.Data.Where((t, i) => rp.Data[i] != t).Any(); + } + return false; } + /// + public override int GetHashCode() + { + // Generated random values. + var hashCode = 1216194372; + hashCode = (hashCode * -1521134295) + this.Type.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.chunkType.GetHashCode(); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.data); + return hashCode; + } + + /// + public override string ToString() + { + return $"{this.Type} - Data: {BitConverter.ToString(this.Data).Replace("-", " ")}"; + } + + /// protected override byte[] EncodeImpl() { var chunkLen = this.data.Length + 1; @@ -80,7 +107,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads { START_BYTE, this.chunkType, - (byte)chunkLen + (byte)chunkLen, }; bytes.AddRange(this.data); @@ -89,6 +116,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs index 8ac8a9770..bb50f6a02 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs @@ -1,30 +1,33 @@ using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads { +namespace Dalamud.Game.Text.SeStringHandling.Payloads +{ /// - /// A wrapped '–' + /// A wrapped '–'. /// - public class SeHyphenPayload : Payload, ITextProvider { - + public class SeHyphenPayload : Payload, ITextProvider + { + private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE }; + /// - /// Instance of SeHyphenPayload + /// Gets an instance of SeHyphenPayload. /// - public static SeHyphenPayload Payload => new SeHyphenPayload(); + public static SeHyphenPayload Payload => new(); - /// + /// + /// Gets the text, just a '–'. + /// + public string Text => "–"; + + /// public override PayloadType Type => PayloadType.SeHyphen; - private readonly byte[] bytes = {START_BYTE, (byte) SeStringChunkType.SeHyphen, 0x01, END_BYTE}; - /// protected override byte[] EncodeImpl() => this.bytes; /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) { } - - /// - /// Just a '–' - /// - public string Text => "–"; + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs index da7dd30cf..6adf988dd 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs @@ -1,8 +1,8 @@ -using Lumina.Excel.GeneratedSheets; -using System; using System.Collections.Generic; using System.IO; + using Dalamud.Data; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads @@ -12,45 +12,50 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class StatusPayload : Payload { - public override PayloadType Type => PayloadType.Status; - private Status status; - /// - /// The Lumina Status object represented by this payload. - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public Status Status - { - get - { - status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); - return status; - } - } [JsonProperty] private uint statusId; - internal StatusPayload() { } - /// + /// Initializes a new instance of the class. /// Creates a new StatusPayload for the given status id. /// /// DataManager instance needed to resolve game data. /// The id of the Status for this link. - public StatusPayload(DataManager data, uint statusId) { + public StatusPayload(DataManager data, uint statusId) + { this.DataResolver = data; this.statusId = statusId; } - public override string ToString() + /// + /// Initializes a new instance of the class. + /// Creates a new StatusPayload for the given status id. + /// + internal StatusPayload() { - return $"{Type} - StatusId: {statusId}, Name: {Status.Name}"; } + /// + public override PayloadType Type => PayloadType.Status; + + /// + /// Gets the Lumina Status object represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Status Status => this.status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); + + /// + public override string ToString() + { + return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}"; + } + + /// protected override byte[] EncodeImpl() { var idBytes = MakeInteger(this.statusId); @@ -58,7 +63,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads var chunkLen = idBytes.Length + 7; var bytes = new List() { - START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status + START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status, }; bytes.AddRange(idBytes); @@ -68,6 +73,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { this.statusId = GetInteger(reader); diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs index 15525018a..9d05fb440 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs @@ -1,9 +1,9 @@ -using Newtonsoft.Json; -using System; using System.Collections.Generic; using System.IO; using System.Text; +using Newtonsoft.Json; + namespace Dalamud.Game.Text.SeStringHandling.Payloads { /// @@ -11,34 +11,11 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class TextPayload : Payload, ITextProvider { - public override PayloadType Type => PayloadType.RawText; - - // allow modifying the text of existing payloads on the fly [JsonProperty] private string text; - /// - /// The text contained in this payload. - /// This may contain SE's special unicode characters. - /// - [JsonIgnore] - public string Text - { - get { return this.text; } - set - { - this.text = value; - Dirty = true; - } - } - - public override string ToString() - { - return $"{Type} - Text: {Text}"; - } - - internal TextPayload() { } /// + /// Initializes a new instance of the class. /// Creates a new TextPayload for the given text. /// /// The text to include for this payload. @@ -47,6 +24,43 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads this.text = text; } + /// + /// Initializes a new instance of the class. + /// Creates a new TextPayload for the given text. + /// + internal TextPayload() + { + } + + /// + public override PayloadType Type => PayloadType.RawText; + + /// + /// Gets or sets the text contained in this payload. + /// This may contain SE's special unicode characters. + /// + [JsonIgnore] + public string Text + { + get + { + return this.text; + } + + set + { + this.text = value; + this.Dirty = true; + } + } + + /// + public override string ToString() + { + return $"{this.Type} - Text: {this.Text}"; + } + + /// protected override byte[] EncodeImpl() { // special case to allow for empty text payloads, so users don't have to check @@ -59,6 +73,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return Encoding.UTF8.GetBytes(this.text); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { var textBytes = new List(); diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs index 9f705fc6c..61a0599c9 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -1,8 +1,8 @@ -using Lumina.Excel.GeneratedSheets; -using System; using System.Collections.Generic; using System.IO; + using Dalamud.Data; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads @@ -12,83 +12,86 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class UIForegroundPayload : Payload { - /// - /// Payload representing disabling foreground color on following text. - /// - // TODO Make this work with DI - public static UIForegroundPayload UIForegroundOff => new UIForegroundPayload(null, 0); - - public override PayloadType Type => PayloadType.UIForeground; - - /// - /// Whether or not this payload represents applying a foreground color, or disabling one. - /// - public bool IsEnabled => ColorKey != 0; - private UIColor color; - /// - /// A Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public UIColor UIColor - { - get - { - this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); - return this.color; - } - } - - /// - /// The color key used as a lookup in the UIColor table for this foreground color. - /// - [JsonIgnore] - public ushort ColorKey - { - get { return this.colorKey; } - set - { - this.colorKey = value; - this.color = null; - Dirty = true; - } - } - - /// - /// The Red/Green/Blue values for this foreground color, encoded as a typical hex color. - /// - [JsonIgnore] - public uint RGB - { - get - { - return (UIColor.UIForeground & 0xFFFFFF); - } - } [JsonProperty] private ushort colorKey; - internal UIForegroundPayload() { } - /// + /// Initializes a new instance of the class. /// Creates a new UIForegroundPayload for the given UIColor key. /// /// DataManager instance needed to resolve game data. - /// - public UIForegroundPayload(DataManager data, ushort colorKey) { + /// A UIColor key. + public UIForegroundPayload(DataManager data, ushort colorKey) + { this.DataResolver = data; this.colorKey = colorKey; } - public override string ToString() + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + internal UIForegroundPayload() { - return $"{Type} - UIColor: {colorKey} color: {(IsEnabled ? RGB : 0)}"; } + /// + /// Gets a payload representing disabling foreground color on following text. + /// + // TODO Make this work with DI + public static UIForegroundPayload UIForegroundOff => new(null, 0); + + /// + public override PayloadType Type => PayloadType.UIForeground; + + /// + /// Gets a value indicating whether or not this payload represents applying a foreground color, or disabling one. + /// + public bool IsEnabled => this.ColorKey != 0; + + /// + /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + + /// + /// Gets or sets the color key used as a lookup in the UIColor table for this foreground color. + /// + [JsonIgnore] + public ushort ColorKey + { + get + { + return this.colorKey; + } + + set + { + this.colorKey = value; + this.color = null; + this.Dirty = true; + } + } + + /// + /// Gets the Red/Green/Blue values for this foreground color, encoded as a typical hex color. + /// + [JsonIgnore] + public uint RGB => this.UIColor.UIForeground & 0xFFFFFF; + + /// + public override string ToString() + { + return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; + } + + /// protected override byte[] EncodeImpl() { var colorBytes = MakeInteger(this.colorKey); @@ -96,7 +99,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads var bytes = new List(new byte[] { - START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen + START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen, }); bytes.AddRange(colorBytes); @@ -105,6 +108,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { this.colorKey = (ushort)GetInteger(reader); diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs index 689423f6f..4a716c99d 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs @@ -1,8 +1,8 @@ -using Lumina.Excel.GeneratedSheets; -using System; using System.Collections.Generic; using System.IO; + using Dalamud.Data; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads @@ -12,83 +12,86 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads /// public class UIGlowPayload : Payload { - /// - /// Payload representing disabling glow color on following text. - /// - // TODO Make this work with DI - public static UIGlowPayload UIGlowOff => new UIGlowPayload(null, 0); - - public override PayloadType Type => PayloadType.UIGlow; - - /// - /// Whether or not this payload represents applying a glow color, or disabling one. - /// - public bool IsEnabled => ColorKey != 0; - private UIColor color; - /// - /// A Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public UIColor UIColor - { - get - { - this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); - return this.color; - } - } - - /// - /// The color key used as a lookup in the UIColor table for this glow color. - /// - [JsonIgnore] - public ushort ColorKey - { - get { return this.colorKey; } - set - { - this.colorKey = value; - this.color = null; - Dirty = true; - } - } - - /// - /// The Red/Green/Blue values for this glow color, encoded as a typical hex color. - /// - [JsonIgnore] - public uint RGB - { - get - { - return (UIColor.UIGlow & 0xFFFFFF); - } - } [JsonProperty] private ushort colorKey; - internal UIGlowPayload() { } - /// + /// Initializes a new instance of the class. /// Creates a new UIForegroundPayload for the given UIColor key. /// /// DataManager instance needed to resolve game data. - /// - public UIGlowPayload(DataManager data, ushort colorKey) { + /// A UIColor key. + public UIGlowPayload(DataManager data, ushort colorKey) + { this.DataResolver = data; this.colorKey = colorKey; } - public override string ToString() + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + internal UIGlowPayload() { - return $"{Type} - UIColor: {colorKey} color: {(IsEnabled ? RGB : 0)}"; } + /// + /// Gets a payload representing disabling glow color on following text. + /// + // TODO Make this work with DI + public static UIGlowPayload UIGlowOff => new(null, 0); + + /// + public override PayloadType Type => PayloadType.UIGlow; + + /// + /// Gets or sets the color key used as a lookup in the UIColor table for this glow color. + /// + [JsonIgnore] + public ushort ColorKey + { + get + { + return this.colorKey; + } + + set + { + this.colorKey = value; + this.color = null; + this.Dirty = true; + } + } + + /// + /// Gets a value indicating whether or not this payload represents applying a glow color, or disabling one. + /// + public bool IsEnabled => this.ColorKey != 0; + + /// + /// Gets the Red/Green/Blue values for this glow color, encoded as a typical hex color. + /// + [JsonIgnore] + public uint RGB => this.UIColor.UIGlow & 0xFFFFFF; + + /// + /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + + /// + public override string ToString() + { + return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; + } + + /// protected override byte[] EncodeImpl() { var colorBytes = MakeInteger(this.colorKey); @@ -96,7 +99,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads var bytes = new List(new byte[] { - START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen + START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen, }); bytes.AddRange(colorBytes); @@ -105,6 +108,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads return bytes.ToArray(); } + /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { this.colorKey = (ushort)GetInteger(reader); diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index baae3b153..43ae7600e 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -1,8 +1,7 @@ -using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; + using Dalamud.Data; using Dalamud.Game.Text.SeStringHandling.Payloads; using Newtonsoft.Json; @@ -15,21 +14,42 @@ namespace Dalamud.Game.Text.SeStringHandling public class SeString { /// - /// The ordered list of payloads included in this SeString. + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. + /// + /// The Payload objects to make up this string. + [JsonConstructor] + public SeString(List payloads) + { + this.Payloads = payloads; + } + + /// + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. + /// + /// The Payload objects to make up this string. + public SeString(Payload[] payloads) + { + this.Payloads = new List(payloads); + } + + /// + /// Gets the ordered list of payloads included in this SeString. /// public List Payloads { get; } /// - /// Helper function to get all raw text from a message as a single joined string + /// Gets all of the raw text from a message as a single joined string. /// /// - /// All the raw text from the contained payloads, joined into a single string + /// All the raw text from the contained payloads, joined into a single string. /// public string TextValue { get { - return Payloads + return this.Payloads .Where(p => p is ITextProvider) .Cast() .Aggregate(new StringBuilder(), (sb, tp) => sb.Append(tp.Text), sb => sb.ToString()); @@ -39,27 +59,45 @@ namespace Dalamud.Game.Text.SeStringHandling /// /// Implicitly convert a string into a SeString containing a . /// - /// string to convert - /// Equivalent SeString - public static implicit operator SeString(string str) => new SeString(new Payload[] { new TextPayload(str) }); + /// string to convert. + /// Equivalent SeString. + public static implicit operator SeString(string str) => new(new Payload[] { new TextPayload(str) }); /// - /// Creates a new SeString from an ordered list of payloads. + /// Creates a SeString from a json. (For testing - not recommended for production use.) /// - /// The Payload objects to make up this string. - [JsonConstructor] - public SeString(List payloads) + /// A serialized SeString produced by ToJson() . + /// An initialized instance of DataManager for Lumina queries. + /// A SeString initialized with values from the json. + public static SeString FromJson(string json, DataManager dataManager) { - Payloads = payloads; + var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + TypeNameHandling = TypeNameHandling.Auto, + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, + }); + + foreach (var payload in s.Payloads) + { + payload.DataResolver = dataManager; + } + + return s; } /// - /// Creates a new SeString from an ordered list of payloads. + /// Serializes the SeString to json. /// - /// The Payload objects to make up this string. - public SeString(Payload[] payloads) + /// An json representation of this object. + public string ToJson() { - Payloads = new List(payloads); + return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + TypeNameHandling = TypeNameHandling.Auto, + }); } /// @@ -69,7 +107,7 @@ namespace Dalamud.Game.Text.SeStringHandling /// This object. public SeString Append(SeString other) { - Payloads.AddRange(other.Payloads); + this.Payloads.AddRange(other.Payloads); return this; } @@ -80,7 +118,7 @@ namespace Dalamud.Game.Text.SeStringHandling /// This object. public SeString Append(List payloads) { - Payloads.AddRange(payloads); + this.Payloads.AddRange(payloads); return this; } @@ -91,7 +129,7 @@ namespace Dalamud.Game.Text.SeStringHandling /// This object. public SeString Append(Payload payload) { - Payloads.Add(payload); + this.Payloads.Add(payload); return this; } @@ -103,7 +141,7 @@ namespace Dalamud.Game.Text.SeStringHandling public byte[] Encode() { var messageBytes = new List(); - foreach (var p in Payloads) + foreach (var p in this.Payloads) { messageBytes.AddRange(p.Encode()); } @@ -114,46 +152,10 @@ namespace Dalamud.Game.Text.SeStringHandling /// /// Get the text value of this SeString. /// - /// The TextValue property - public override string ToString() { - return TextValue; - } - - /// - /// Serializes the SeString to json - /// - /// An json representation of this object - public string ToJson() + /// The TextValue property. + public override string ToString() { - return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() - { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, - TypeNameHandling = TypeNameHandling.Auto - }); - } - - /// - /// Creates a SeString from a json. (For testing - not recommended for production use.) - /// - /// A serialized SeString produced by ToJson() - /// An initialized instance of DataManager for Lumina queries. - /// A SeString initialized with values from the json - public static SeString FromJson(string json, DataManager dataManager) - { - var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings - { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - TypeNameHandling = TypeNameHandling.Auto, - ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor - }); - - foreach(var payload in s.Payloads) - { - payload.DataResolver = dataManager; - } - - return s; + return this.TextValue; } } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs index 25f1d43c2..7b6f45e07 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs @@ -1,22 +1,27 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; + using Dalamud.Data; using Dalamud.Game.Text.SeStringHandling.Payloads; using Lumina.Excel.GeneratedSheets; namespace Dalamud.Game.Text.SeStringHandling { + /// + /// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components. + /// public class SeStringManager { private readonly DataManager data; - public SeStringManager(DataManager Data) { - this.data = Data; + /// + /// Initializes a new instance of the class. + /// + /// The DataManager instance. + public SeStringManager(DataManager data) + { + this.data = data; } /// @@ -51,7 +56,7 @@ namespace Dalamud.Game.Text.SeStringHandling /// An SeString containing all the payloads necessary to display an item link in the chat log. public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null) { - string displayName = displayNameOverride ?? this.data.GetExcelSheet().GetRow(itemId).Name; + var displayName = displayNameOverride ?? this.data.GetExcelSheet().GetRow(itemId).Name; if (isHQ) { displayName += $" {(char)SeIconChar.HighQuality}"; @@ -65,11 +70,11 @@ namespace Dalamud.Game.Text.SeStringHandling new ItemPayload(this.data, itemId, isHQ), // arrow goes here new TextPayload(displayName), - RawPayload.LinkTerminator + RawPayload.LinkTerminator, // sometimes there is another set of uiglow/foreground off payloads here // might be necessary when including additional text after the item name }); - payloads.InsertRange(3, TextArrowPayloads()); + payloads.InsertRange(3, this.TextArrowPayloads()); return new SeString(payloads); } @@ -83,9 +88,17 @@ namespace Dalamud.Game.Text.SeStringHandling /// An SeString containing all the payloads necessary to display an item link in the chat log. public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null) { - return CreateItemLink((uint)item.RowId, isHQ, displayNameOverride ?? item.Name); + return this.CreateItemLink((uint)item.RowId, isHQ, displayNameOverride ?? item.Name); } + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The raw x-coordinate for this link. + /// The raw y-coordinate for this link.. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) { var mapPayload = new MapLinkPayload(this.data, territoryId, mapId, rawX, rawY); @@ -96,9 +109,9 @@ namespace Dalamud.Game.Text.SeStringHandling mapPayload, // arrow goes here new TextPayload(nameString), - RawPayload.LinkTerminator + RawPayload.LinkTerminator, }); - payloads.InsertRange(1, TextArrowPayloads()); + payloads.InsertRange(1, this.TextArrowPayloads()); return new SeString(payloads); } @@ -122,9 +135,9 @@ namespace Dalamud.Game.Text.SeStringHandling mapPayload, // arrow goes here new TextPayload(nameString), - RawPayload.LinkTerminator + RawPayload.LinkTerminator, }); - payloads.InsertRange(1, TextArrowPayloads()); + payloads.InsertRange(1, this.TextArrowPayloads()); return new SeString(payloads); } @@ -147,10 +160,10 @@ namespace Dalamud.Game.Text.SeStringHandling foreach (var place in matches) { - var map = mapSheet.GetRows().FirstOrDefault(row => row.PlaceName.Row == place.RowId); + var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId); if (map != null) { - return CreateMapLink(map.TerritoryType.Row, (uint)map.RowId, xCoord, yCoord, fudgeFactor); + return this.CreateMapLink(map.TerritoryType.Row, map.RowId, xCoord, yCoord, fudgeFactor); } } @@ -171,7 +184,7 @@ namespace Dalamud.Game.Text.SeStringHandling new UIGlowPayload(this.data, 0x01F5), new TextPayload($"{(char)SeIconChar.LinkMarker}"), UIGlowPayload.UIGlowOff, - UIForegroundPayload.UIForegroundOff + UIForegroundPayload.UIForegroundOff, }); } } diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index 4a4cc28af..dc8005f6b 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -2,15 +2,34 @@ using System; namespace Dalamud.Game.Text { - public sealed class XivChatEntry { + /// + /// This class represents a single chat log entry. + /// + public sealed class XivChatEntry + { + /// + /// Gets or sets the type of entry. + /// public XivChatType Type { get; set; } = XivChatType.Debug; + /// + /// Gets or sets the sender ID. + /// public uint SenderId { get; set; } + /// + /// Gets or sets the sender name. + /// public string Name { get; set; } = string.Empty; + /// + /// Gets or sets the message bytes. + /// public byte[] MessageBytes { get; set; } + /// + /// Gets or sets the message parameters. + /// public IntPtr Parameters { get; set; } } } diff --git a/Dalamud/Game/Text/XivChatType.cs b/Dalamud/Game/Text/XivChatType.cs index 54d609d6b..9658078f0 100644 --- a/Dalamud/Game/Text/XivChatType.cs +++ b/Dalamud/Game/Text/XivChatType.cs @@ -1,142 +1,304 @@ using System; using System.Linq; -#pragma warning disable 1591 namespace Dalamud.Game.Text { /// - /// The FFXIV chat types as seen in the LogKind ex table. + /// The FFXIV chat types as seen in the LogKind ex table. /// - public enum XivChatType : ushort // FIXME: this is a single byte + public enum XivChatType : ushort // FIXME: this is a single byte { + /// + /// No chat type. + /// None = 0, + + /// + /// The debug chat type. + /// Debug = 1, + /// + /// The urgent chat type. + /// [XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)] Urgent = 2, + /// + /// The notice chat type. + /// [XivChatTypeInfo("Notice", "notice", 0xFF9400D3)] Notice = 3, + /// + /// The say chat type. + /// [XivChatTypeInfo("Say", "say", 0xFFFFFFFF)] Say = 10, + /// + /// The shout chat type. + /// [XivChatTypeInfo("Shout", "shout", 0xFFFF4500)] Shout = 11, + + /// + /// The outgoing tell chat type. + /// TellOutgoing = 12, + /// + /// The incoming tell chat type. + /// [XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)] TellIncoming = 13, + /// + /// The party chat type. + /// [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] Party = 14, + /// + /// The alliance chat type. + /// [XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)] Alliance = 15, + /// + /// The linkshell 1 chat type. + /// [XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)] Ls1 = 16, + /// + /// The linkshell 2 chat type. + /// [XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)] Ls2 = 17, + /// + /// The linkshell 3 chat type. + /// [XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)] Ls3 = 18, + /// + /// The linkshell 4 chat type. + /// [XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)] Ls4 = 19, + /// + /// The linkshell 5 chat type. + /// [XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)] Ls5 = 20, + /// + /// The linkshell 6 chat type. + /// [XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)] Ls6 = 21, + /// + /// The linkshell 7 chat type. + /// [XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)] Ls7 = 22, + /// + /// The linkshell 8 chat type. + /// [XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)] Ls8 = 23, + /// + /// The free company chat type. + /// [XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)] FreeCompany = 24, + /// + /// The novice network chat type. + /// [XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)] NoviceNetwork = 27, + /// + /// The custom emotes chat type. + /// [XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)] CustomEmote = 28, + + /// + /// The standard emotes chat type. + /// [XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)] StandardEmote = 29, + /// + /// The yell chat type. + /// [XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)] Yell = 30, + /// + /// The cross-world party chat type. + /// [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] CrossParty = 32, + /// + /// The PvP team chat type. + /// [XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)] PvPTeam = 36, + /// + /// The cross-world linkshell chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)] CrossLinkShell1 = 37, + /// + /// The echo chat type. + /// [XivChatTypeInfo("Echo", "echo", 0xFF808080)] Echo = 56, + + /// + /// The system error chat type. + /// SystemError = 58, + /// + /// The system message chat type. + /// SystemMessage = 57, + + /// + /// The system message (gathering) chat type. + /// GatheringSystemMessage = 59, + + /// + /// The error message chat type. + /// ErrorMessage = 60, - // not sure if this is used for anything else + + /// + /// The retainer sale chat type. + /// + /// + /// This might be used for other purposes. + /// RetainerSale = 71, + /// + /// The cross-world linkshell 2 chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)] CrossLinkShell2 = 101, + /// + /// The cross-world linkshell 3 chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)] CrossLinkShell3 = 102, + /// + /// The cross-world linkshell 4 chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)] CrossLinkShell4 = 103, + /// + /// The cross-world linkshell 5 chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)] CrossLinkShell5 = 104, + /// + /// The cross-world linkshell 6 chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)] CrossLinkShell6 = 105, + /// + /// The cross-world linkshell 7 chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)] CrossLinkShell7 = 106, + /// + /// The cross-world linkshell 8 chat type. + /// [XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)] - CrossLinkShell8 = 107 + CrossLinkShell8 = 107, } + /// + /// Extension methods for the type. + /// public static class XivChatTypeExtensions { - public static XivChatTypeInfoAttribute GetDetails(this XivChatType p) + /// + /// Get the InfoAttribute associated with this chat type. + /// + /// The chat type. + /// The info attribute. + public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType) { - return p.GetAttribute(); + return chatType.GetAttribute(); } } + /// + /// Storage for relevant information associated with the chat type. + /// public class XivChatTypeInfoAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + /// The fancy name. + /// The name slug. + /// The default color. internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor) { - FancyName = fancyName; - Slug = slug; - DefaultColor = defaultColor; + this.FancyName = fancyName; + this.Slug = slug; + this.DefaultColor = defaultColor; } + /// + /// Gets the "fancy" name of the type. + /// public string FancyName { get; } + + /// + /// Gets the type name slug or short-form. + /// public string Slug { get; } + + /// + /// Gets the type default color. + /// public uint DefaultColor { get; } } + /// + /// Extension methods for enums. + /// public static class EnumExtensions { + /// + /// Gets an attribute on an enum. + /// + /// The type of attribute to get. + /// The enum value that has an attached attribute. + /// The attached attribute, if any. public static TAttribute GetAttribute(this Enum value) where TAttribute : Attribute { diff --git a/Dalamud/GlobalSuppressions.cs b/Dalamud/GlobalSuppressions.cs index 8dc2d85d2..e4f2f5e86 100644 --- a/Dalamud/GlobalSuppressions.cs +++ b/Dalamud/GlobalSuppressions.cs @@ -5,8 +5,110 @@ using System.Diagnostics.CodeAnalysis; +// General +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Preventing long lines", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "I like regions", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1123:Do not place regions within elements", Justification = "I like regions in elements too", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "This is annoying", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] +[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:Single-line comments should not be followed by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] +[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Preventing long lines", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] + +// Extensions +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Interface.FontAwesomeExtensions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Interface.FontAwesomeExtensions")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlagsExtensions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlagsExtensions")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeExtensions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeExtensions")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group attributes with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeInfoAttribute")] + +// NativeFunctions.cs +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Default Microsoft naming", Scope = "type", Target = "~T:Dalamud.NativeFunctions.FlashWindowInfo")] + +// EntryPoint.cs +[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Required by EasyHook", Scope = "member", Target = "~M:Dalamud.EntryPoint.#ctor(EasyHook.RemoteHooking.IContext,Dalamud.DalamudStartInfo)")] +[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Required by EasyHook", Scope = "member", Target = "~M:Dalamud.EntryPoint.Run(EasyHook.RemoteHooking.IContext,Dalamud.DalamudStartInfo)")] + +// DalamudStartInfo.cs +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Interop", Scope = "type", Target = "~T:Dalamud.DalamudStartInfo")] + +// AtkResNode.cs +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Inherit documentation or lack thereof from FFXIVClientStructs", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AtkResNode")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Inherit naming from FFXIVClientStructs", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AtkResNode")] + +// PartyFinder +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Explicit struct layout", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Packet")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Explicit struct layout", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Listing")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Packet")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Listing")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.PartyMember")] + +// Offsets.cs +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:Static elements should appear before instance elements", Justification = "Offset classes goto the end of file.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group offset classes with the relevant class.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group offset classes with the relevant class.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.ActorOffsets")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group offset classes with the relevant class.", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AddonOffsets")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.ActorOffsets")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AddonOffsets")] + +// Breaking api changes: these should be split into a PartyFinder subdirectory +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.SearchAreaFlags")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlags")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.ObjectiveFlags")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.ConditionFlags")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.DutyFinderSettingsFlags")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.LootRuleFlags")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.Category")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.DutyType")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.PartyFinderListingEventArgs")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.PartyMember")] +[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "breaking api change", Scope = "member", Target = "~E:Dalamud.Game.Internal.Gui.PartyFinderGui.ReceiveListing")] + +// Breaking api changes +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.Actors.Resolvers.ClassJob.Id")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.Actors.Resolvers.World.Id")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.Actors.Types.Actor.Address")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.BaseAddressResolver.DebugScannedValues")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.Addon.Addon.Address")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.Addon.Addon.addonStruct")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.GameGui.GetBaseUIObject")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.DataResolver")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.Actors")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.TerritoryType")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.TerritoryChanged")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.ClientLanguage")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.JobGauges")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.PartyList")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.KeyState")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.GamepadState")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.Condition")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.ClientState.Targets")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Interface.InterfaceManager.LastImGuiIoPtr")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.Types.PartyMember")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Interface.InterfaceManager.OnBuildFonts")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.Actors.Types.Actor.Address")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.Structs.JobGauge.BLMGauge.NumUmbralHearts")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.Structs.JobGauge.DNCGauge.NumCompleteSteps")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.ClientState.Structs.JobGauge.DRKGauge.ShadowTimeRemaining")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.START_BYTE")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.END_BYTE")] +[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Unused, but eventually, maybe.", Scope = "member", Target = "~F:Dalamud.Game.ClientState.PartyList.address")] +[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "breaking api change", Scope = "member", Target = "~E:Dalamud.Game.ClientState.ClientState.CfPop")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:Static elements should appear before instance elements", Justification = "breaking api change, move to util", Scope = "type", Target = "~T:Dalamud.Game.Text.EnumExtensions")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change, move to util", Scope = "type", Target = "~T:Dalamud.Game.Text.EnumExtensions")] + +// I mostly didnt care to do these. +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Network.GameNetwork.OnNetworkMessage")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardCurrentOfferings")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardCurrentOfferings")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardHistory")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardHistory")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketTaxRates")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketTaxRates")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.MarketBoardItemRequest")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.MarketBoardItemRequest")] diff --git a/Dalamud/Hooking/HookInfo.cs b/Dalamud/Hooking/HookInfo.cs index b4d579d39..85a815c0d 100644 --- a/Dalamud/Hooking/HookInfo.cs +++ b/Dalamud/Hooking/HookInfo.cs @@ -13,7 +13,7 @@ namespace Dalamud.Hooking /// /// Static list of tracked and registered hooks. /// - internal static readonly List TrackedHooks = new List(); + internal static readonly List TrackedHooks = new(); private ulong? inProcessMemory = 0; diff --git a/Dalamud/Interface/DalamudChangelogWindow.cs b/Dalamud/Interface/DalamudChangelogWindow.cs index 27ab1f8fb..3d01bef83 100644 --- a/Dalamud/Interface/DalamudChangelogWindow.cs +++ b/Dalamud/Interface/DalamudChangelogWindow.cs @@ -1,24 +1,34 @@ -using System; using System.Diagnostics; -using System.Linq; using System.Numerics; + using Dalamud.Interface.Windowing; -using Dalamud.Plugin; using ImGuiNET; -using Lumina.Data.Parsing.Layer; - -namespace Dalamud.Interface { - class DalamudChangelogWindow : Window { - private readonly Dalamud dalamud; - private string assemblyVersion = Util.AssemblyVersion; +namespace Dalamud.Interface +{ + /// + /// For major updates, an in-game Changelog window. + /// + internal class DalamudChangelogWindow : Window + { + /// + /// Whether the latest update warrants a changelog window. + /// public const bool WarrantsChangelog = false; + private const string ChangeLog = @"* Various behind-the-scenes changes to improve stability * Faster startup times If you note any issues or need help, please make sure to ask on our discord server."; + private readonly Dalamud dalamud; + private string assemblyVersion = Util.AssemblyVersion; + + /// + /// Initializes a new instance of the class. + /// + /// The Dalamud instance. public DalamudChangelogWindow(Dalamud dalamud) : base("What's new in XIVLauncher?", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize) { @@ -29,7 +39,9 @@ If you note any issues or need help, please make sure to ask on our discord serv this.IsOpen = WarrantsChangelog; } - public override void Draw() { + /// + public override void Draw() + { ImGui.Text($"The in-game addon has been updated to version D{this.assemblyVersion}."); ImGui.Dummy(new Vector2(10, 10) * ImGui.GetIO().FontGlobalScale); @@ -45,21 +57,23 @@ If you note any issues or need help, please make sure to ask on our discord serv ImGui.PushFont(InterfaceManager.IconFont); - if (ImGui.Button(FontAwesomeIcon.Download.ToIconString())) + if (ImGui.Button(FontAwesomeIcon.Download.ToIconString())) this.dalamud.DalamudUi.OpenPluginInstaller(); - if (ImGui.IsItemHovered()) { + if (ImGui.IsItemHovered()) + { ImGui.PopFont(); ImGui.SetTooltip("Open Plugin Installer"); ImGui.PushFont(InterfaceManager.IconFont); } - + ImGui.SameLine(); - if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString())) + if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString())) Process.Start("https://discord.gg/3NMcUV5"); - if (ImGui.IsItemHovered()) { + if (ImGui.IsItemHovered()) + { ImGui.PopFont(); ImGui.SetTooltip("Join our Discord server"); ImGui.PushFont(InterfaceManager.IconFont); @@ -70,12 +84,12 @@ If you note any issues or need help, please make sure to ask on our discord serv if (ImGui.Button(FontAwesomeIcon.Globe.ToIconString())) Process.Start("https://github.com/goatcorp/FFXIVQuickLauncher"); - if (ImGui.IsItemHovered()) { + if (ImGui.IsItemHovered()) + { ImGui.PopFont(); ImGui.SetTooltip("See our GitHub repository"); ImGui.PushFont(InterfaceManager.IconFont); } - ImGui.PopFont(); diff --git a/Dalamud/Interface/DalamudCreditsWindow.cs b/Dalamud/Interface/DalamudCreditsWindow.cs index cfe01a8f1..d15c64909 100644 --- a/Dalamud/Interface/DalamudCreditsWindow.cs +++ b/Dalamud/Interface/DalamudCreditsWindow.cs @@ -2,15 +2,19 @@ using System; using System.IO; using System.Linq; using System.Numerics; + using Dalamud.Game.Internal; using Dalamud.Interface.Windowing; using ImGuiNET; using ImGuiScene; -using Serilog; namespace Dalamud.Interface { - class DalamudCreditsWindow : Window, IDisposable { + /// + /// A window documenting contributors to the project. + /// + internal class DalamudCreditsWindow : Window, IDisposable + { private const string CreditsTextTempl = @" Dalamud A FFXIV Hooking Framework @@ -104,6 +108,10 @@ Thank you for using XIVLauncher and Dalamud! private string creditsText; + /// + /// Initializes a new instance of the class. + /// + /// The Dalamud instance. public DalamudCreditsWindow(Dalamud dalamud) : base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize, true) { @@ -120,6 +128,7 @@ Thank you for using XIVLauncher and Dalamud! this.BgAlpha = 0.5f; } + /// public override void OnOpen() { base.OnOpen(); @@ -132,6 +141,7 @@ Thank you for using XIVLauncher and Dalamud! this.framework.Gui.SetBgm(132); } + /// public override void OnClose() { base.OnClose(); @@ -139,22 +149,20 @@ Thank you for using XIVLauncher and Dalamud! this.framework.Gui.SetBgm(9999); } - public void Dispose() { - this.logoTexture?.Dispose(); - } - - public override void Draw() { + /// + public override void Draw() + { var screenSize = ImGui.GetMainViewport().Size; var windowSize = ImGui.GetWindowSize(); - this.Position = new Vector2((screenSize.X / 2) - windowSize.X / 2, (screenSize.Y / 2) - windowSize.Y / 2); + this.Position = new Vector2((screenSize.X / 2) - (windowSize.X / 2), (screenSize.Y / 2) - (windowSize.Y / 2)); ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.NoScrollbar); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); ImGui.Dummy(new Vector2(0, 340f) * ImGui.GetIO().FontGlobalScale); - ImGui.Text(""); + ImGui.Text(string.Empty); ImGui.SameLine(150f); ImGui.Image(this.logoTexture.ImGuiHandle, new Vector2(190f, 190f) * ImGui.GetIO().FontGlobalScale); @@ -163,10 +171,11 @@ Thank you for using XIVLauncher and Dalamud! var windowX = ImGui.GetWindowSize().X; - foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) { + foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) + { var lineLenX = ImGui.CalcTextSize(creditsLine).X; - ImGui.Dummy(new Vector2((windowX / 2) - lineLenX / 2, 0f)); + ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f)); ImGui.SameLine(); ImGui.TextUnformatted(creditsLine); } @@ -183,5 +192,13 @@ Thank you for using XIVLauncher and Dalamud! ImGui.EndChild(); } + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.logoTexture?.Dispose(); + } } } diff --git a/Dalamud/Interface/DalamudDataWindow.cs b/Dalamud/Interface/DalamudDataWindow.cs index 460132394..e34e68dae 100644 --- a/Dalamud/Interface/DalamudDataWindow.cs +++ b/Dalamud/Interface/DalamudDataWindow.cs @@ -248,7 +248,7 @@ namespace Dalamud.Interface // Condition case 6: #if DEBUG - ImGui.Text($"ptr: {this.dalamud.ClientState.Condition.conditionArrayBase.ToString("X16")}"); + ImGui.Text($"ptr: {this.dalamud.ClientState.Condition.ConditionArrayBase.ToString("X16")}"); #endif ImGui.Text("Current Conditions:"); @@ -294,11 +294,9 @@ namespace Dalamud.Interface // Addon Inspector case 10: - { this.addonInspector ??= new UIDebug(this.dalamud); this.addonInspector.Draw(); break; - } // StartInfo case 11: @@ -478,18 +476,13 @@ namespace Dalamud.Interface } else { - stateString += - $"FrameworkBase: {this.dalamud.Framework.Address.BaseAddress.ToInt64():X}\n"; - + stateString += $"FrameworkBase: {this.dalamud.Framework.Address.BaseAddress.ToInt64():X}\n"; stateString += $"ActorTableLen: {this.dalamud.ClientState.Actors.Length}\n"; stateString += $"LocalPlayerName: {this.dalamud.ClientState.LocalPlayer.Name}\n"; - stateString += - $"CurrentWorldName: {(this.resolveGameData ? this.dalamud.ClientState.LocalPlayer.CurrentWorld.GameData.Name : this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; - stateString += - $"HomeWorldName: {(this.resolveGameData ? this.dalamud.ClientState.LocalPlayer.HomeWorld.GameData.Name : this.dalamud.ClientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; + stateString += $"CurrentWorldName: {(this.resolveGameData ? this.dalamud.ClientState.LocalPlayer.CurrentWorld.GameData.Name : this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; + stateString += $"HomeWorldName: {(this.resolveGameData ? this.dalamud.ClientState.LocalPlayer.HomeWorld.GameData.Name : this.dalamud.ClientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; stateString += $"LocalCID: {this.dalamud.ClientState.LocalContentId:X}\n"; - stateString += - $"LastLinkedItem: {this.dalamud.Framework.Gui.Chat.LastLinkedItemId.ToString()}\n"; + stateString += $"LastLinkedItem: {this.dalamud.Framework.Gui.Chat.LastLinkedItemId}\n"; stateString += $"TerritoryType: {this.dalamud.ClientState.TerritoryType}\n\n"; ImGui.TextUnformatted(stateString); @@ -532,17 +525,17 @@ namespace Dalamud.Interface ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y)); - ImGui.SetNextWindowBgAlpha(Math.Max(1f - actor.YalmDistanceX / this.maxActorDrawDistance, - 0.2f)); - if (ImGui.Begin($"Actor{i}##ActorWindow{i}", - ImGuiWindowFlags.NoDecoration | - ImGuiWindowFlags.AlwaysAutoResize | - ImGuiWindowFlags.NoSavedSettings | - ImGuiWindowFlags.NoMove | - ImGuiWindowFlags.NoMouseInputs | - ImGuiWindowFlags.NoDocking | - ImGuiWindowFlags.NoFocusOnAppearing | - ImGuiWindowFlags.NoNav)) + ImGui.SetNextWindowBgAlpha(Math.Max(1f - (actor.YalmDistanceX / this.maxActorDrawDistance), 0.2f)); + if (ImGui.Begin( + $"Actor{i}##ActorWindow{i}", + ImGuiWindowFlags.NoDecoration | + ImGuiWindowFlags.AlwaysAutoResize | + ImGuiWindowFlags.NoSavedSettings | + ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoMouseInputs | + ImGuiWindowFlags.NoDocking | + ImGuiWindowFlags.NoFocusOnAppearing | + ImGuiWindowFlags.NoNav)) ImGui.Text(actorText); ImGui.End(); } @@ -550,6 +543,7 @@ namespace Dalamud.Interface } } +#pragma warning disable CS0618 // Type or member is obsolete private void DrawIpcDebug() { var i1 = new DalamudPluginInterface(this.dalamud, "DalamudTestSub", null, PluginLoadReason.Boot); @@ -573,9 +567,11 @@ namespace Dalamud.Interface }); } - if (ImGui.Button("Remove test sub")) i1.Unsubscribe("DalamudTestPub"); + if (ImGui.Button("Remove test sub")) + i1.Unsubscribe("DalamudTestPub"); - if (ImGui.Button("Remove test sub any")) i1.UnsubscribeAny(); + if (ImGui.Button("Remove test sub any")) + i1.UnsubscribeAny(); if (ImGui.Button("Send test message")) { @@ -592,9 +588,10 @@ namespace Dalamud.Interface i2.SendMessage("DalamudTestSub", testMsg); } - foreach (var sub in this.dalamud.PluginManager.IpcSubscriptions) - ImGui.Text($"Source:{sub.SourcePluginName} Sub:{sub.SubPluginName}"); + foreach (var (sourcePluginName, subPluginName, subAction) in this.dalamud.PluginManager.IpcSubscriptions) + ImGui.Text($"Source:{sourcePluginName} Sub:{subPluginName}"); } +#pragma warning restore CS0618 // Type or member is obsolete private void DrawAddonDebug() { diff --git a/Dalamud/Interface/DalamudInterface.cs b/Dalamud/Interface/DalamudInterface.cs index 342142e45..30f1534f5 100644 --- a/Dalamud/Interface/DalamudInterface.cs +++ b/Dalamud/Interface/DalamudInterface.cs @@ -37,7 +37,7 @@ namespace Dalamud.Interface private readonly ScratchpadWindow scratchpadWindow; private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; - private readonly WindowSystem windowSystem = new WindowSystem("DalamudCore"); + private readonly WindowSystem windowSystem = new("DalamudCore"); private ulong frameCount = 0; @@ -249,7 +249,7 @@ namespace Dalamud.Interface if (ImGui.MenuItem("Cause AccessViolation")) { - var a = Marshal.ReadByte(IntPtr.Zero); + Marshal.ReadByte(IntPtr.Zero); } ImGui.Separator(); @@ -520,6 +520,7 @@ namespace Dalamud.Interface /// /// Toggle the data window and preset the dropdown. /// + /// The data kind to toggle. internal void ToggleData(string dataKind) { this.dataWindow.IsOpen ^= true; diff --git a/Dalamud/Interface/DalamudLogWindow.cs b/Dalamud/Interface/DalamudLogWindow.cs index 5504f1028..78cbaa17e 100644 --- a/Dalamud/Interface/DalamudLogWindow.cs +++ b/Dalamud/Interface/DalamudLogWindow.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Numerics; -using System.Text; -using System.Threading.Tasks; using Dalamud.Configuration; using Dalamud.Game.Command; @@ -15,17 +12,25 @@ using Serilog.Events; namespace Dalamud.Interface { - class DalamudLogWindow : Window, IDisposable { + /// + /// The window that displays the Dalamud log file in-game. + /// + internal class DalamudLogWindow : Window, IDisposable + { private readonly CommandManager commandManager; private readonly DalamudConfiguration configuration; + private readonly List<(string Line, Vector4 Color)> logText = new(); + private readonly object renderLock = new(); private bool autoScroll; private bool openAtStartup; - private readonly List<(string line, Vector4 color)> logText = new List<(string line, Vector4 color)>(); - - private readonly object renderLock = new object(); private string commandText = string.Empty; + /// + /// Initializes a new instance of the class. + /// + /// The CommandManager instance. + /// The DalamudConfiguration instance. public DalamudLogWindow(CommandManager commandManager, DalamudConfiguration configuration) : base("Dalamud LOG") { @@ -33,56 +38,62 @@ namespace Dalamud.Interface this.configuration = configuration; this.autoScroll = configuration.LogAutoScroll; this.openAtStartup = configuration.LogOpenAtStartup; - SerilogEventSink.Instance.OnLogLine += Serilog_OnLogLine; + SerilogEventSink.Instance.OnLogLine += this.Serilog_OnLogLine; this.Size = new Vector2(500, 400); this.SizeCondition = ImGuiCond.FirstUseEver; } - public void Dispose() { - SerilogEventSink.Instance.OnLogLine -= Serilog_OnLogLine; - } - - private void Serilog_OnLogLine(object sender, (string line, LogEventLevel level) logEvent) + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() { - var color = logEvent.level switch - { - LogEventLevel.Error => ImGuiColors.DalamudRed, - LogEventLevel.Verbose => ImGuiColors.DalamudWhite, - LogEventLevel.Debug => ImGuiColors.DalamudWhite2, - LogEventLevel.Information => ImGuiColors.DalamudWhite, - LogEventLevel.Warning => ImGuiColors.DalamudOrange, - LogEventLevel.Fatal => ImGuiColors.DalamudRed, - _ => throw new ArgumentOutOfRangeException(), - }; - - AddLog(logEvent.line, color); + SerilogEventSink.Instance.OnLogLine -= this.Serilog_OnLogLine; } - public void Clear() { - lock (this.renderLock) { + /// + /// Clear the window of all log entries. + /// + public void Clear() + { + lock (this.renderLock) + { this.logText.Clear(); } } - public void AddLog(string line, Vector4 color) { - lock (this.renderLock) { + /// + /// Add a single log line to the display. + /// + /// The line to add. + /// The line coloring. + public void AddLog(string line, Vector4 color) + { + lock (this.renderLock) + { this.logText.Add((line, color)); } } - public override void Draw() { + /// + public override void Draw() + { // Options menu if (ImGui.BeginPopup("Options")) { - if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) { + if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) + { this.configuration.LogAutoScroll = this.autoScroll; this.configuration.Save(); - }; - if (ImGui.Checkbox("Open at startup", ref this.openAtStartup)) { + } + + if (ImGui.Checkbox("Open at startup", ref this.openAtStartup)) + { this.configuration.LogOpenAtStartup = this.openAtStartup; this.configuration.Save(); - }; + } + ImGui.EndPopup(); } @@ -98,10 +109,14 @@ namespace Dalamud.Interface ImGui.SameLine(); ImGui.InputText("##commandbox", ref this.commandText, 255); ImGui.SameLine(); - if (ImGui.Button("Send")) { - if (this.commandManager.ProcessCommand(this.commandText)) { + if (ImGui.Button("Send")) + { + if (this.commandManager.ProcessCommand(this.commandText)) + { Log.Information("Command was dispatched."); - } else { + } + else + { Log.Information("Command {0} not registered.", this.commandText); } } @@ -109,14 +124,16 @@ namespace Dalamud.Interface ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar); if (clear) - Clear(); + this.Clear(); if (copy) ImGui.LogToClipboard(); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); - lock (this.renderLock) { - foreach (var (line, color) in this.logText) { + lock (this.renderLock) + { + foreach (var (line, color) in this.logText) + { ImGui.TextColored(color, line); } } @@ -128,5 +145,21 @@ namespace Dalamud.Interface ImGui.EndChild(); } + + private void Serilog_OnLogLine(object sender, (string Line, LogEventLevel Level) logEvent) + { + var color = logEvent.Level switch + { + LogEventLevel.Error => ImGuiColors.DalamudRed, + LogEventLevel.Verbose => ImGuiColors.DalamudWhite, + LogEventLevel.Debug => ImGuiColors.DalamudWhite2, + LogEventLevel.Information => ImGuiColors.DalamudWhite, + LogEventLevel.Warning => ImGuiColors.DalamudOrange, + LogEventLevel.Fatal => ImGuiColors.DalamudRed, + _ => throw new ArgumentOutOfRangeException(), + }; + + this.AddLog(logEvent.Line, color); + } } } diff --git a/Dalamud/Interface/DalamudPluginStatWindow.cs b/Dalamud/Interface/DalamudPluginStatWindow.cs index f37102083..fca07d31b 100644 --- a/Dalamud/Interface/DalamudPluginStatWindow.cs +++ b/Dalamud/Interface/DalamudPluginStatWindow.cs @@ -1,47 +1,60 @@ using System; using System.Linq; using System.Reflection; + using Dalamud.Game.Internal; using Dalamud.Hooking; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using ImGuiNET; -namespace Dalamud.Interface { - internal class DalamudPluginStatWindow : Window { - +namespace Dalamud.Interface +{ + /// + /// This window displays plugin statistics for troubleshooting. + /// + internal class DalamudPluginStatWindow : Window + { private readonly PluginManager pluginManager; private bool showDalamudHooks; - public DalamudPluginStatWindow(PluginManager pluginManager) + /// + /// Initializes a new instance of the class. + /// + /// The PluginManager instance. + public DalamudPluginStatWindow(PluginManager pluginManager) : base("Plugin Statistics###DalamudPluginStatWindow") { this.pluginManager = pluginManager; } - public override void Draw() { + /// + public override void Draw() + { ImGui.BeginTabBar("Stat Tabs"); - if (ImGui.BeginTabItem("Draw times")) { + if (ImGui.BeginTabItem("Draw times")) + { + var doStats = UiBuilder.DoStats; - bool doStats = UiBuilder.DoStats; - - if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats)) { + if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats)) + { UiBuilder.DoStats = doStats; } - if (doStats) { - + if (doStats) + { ImGui.SameLine(); - if (ImGui.Button("Reset")) { - foreach (var a in this.pluginManager.Plugins) { + if (ImGui.Button("Reset")) + { + foreach (var a in this.pluginManager.Plugins) + { a.PluginInterface.UiBuilder.LastDrawTime = -1; a.PluginInterface.UiBuilder.MaxDrawTime = -1; a.PluginInterface.UiBuilder.DrawTimeHistory.Clear(); } } - ImGui.Columns(4); ImGui.SetColumnWidth(0, 180f); ImGui.SetColumnWidth(1, 100f); @@ -56,41 +69,49 @@ namespace Dalamud.Interface { ImGui.Text("Average"); ImGui.NextColumn(); ImGui.Separator(); - foreach (var a in this.pluginManager.Plugins) { + foreach (var a in this.pluginManager.Plugins) + { ImGui.Text(a.Definition.Name); ImGui.NextColumn(); - ImGui.Text($"{a.PluginInterface.UiBuilder.LastDrawTime/10000f:F4}ms"); + ImGui.Text($"{a.PluginInterface.UiBuilder.LastDrawTime / 10000f:F4}ms"); ImGui.NextColumn(); - ImGui.Text($"{a.PluginInterface.UiBuilder.MaxDrawTime/10000f:F4}ms"); + ImGui.Text($"{a.PluginInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms"); ImGui.NextColumn(); - if (a.PluginInterface.UiBuilder.DrawTimeHistory.Count > 0) { - ImGui.Text($"{a.PluginInterface.UiBuilder.DrawTimeHistory.Average()/10000f:F4}ms"); - } else { + if (a.PluginInterface.UiBuilder.DrawTimeHistory.Count > 0) + { + ImGui.Text($"{a.PluginInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms"); + } + else + { ImGui.Text("-"); } + ImGui.NextColumn(); } ImGui.Columns(1); - } + ImGui.EndTabItem(); } - if (ImGui.BeginTabItem("Framework times")) { - + if (ImGui.BeginTabItem("Framework times")) + { var doStats = Framework.StatsEnabled; - if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) { + if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) + { Framework.StatsEnabled = doStats; } - if (doStats) { + if (doStats) + { ImGui.SameLine(); - if (ImGui.Button("Reset")) { + if (ImGui.Button("Reset")) + { Framework.StatsHistory.Clear(); } - + ImGui.Columns(4); ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300); ImGui.SetColumnWidth(1, 100f); @@ -107,7 +128,8 @@ namespace Dalamud.Interface { ImGui.Separator(); ImGui.Separator(); - foreach (var handlerHistory in Framework.StatsHistory) { + foreach (var handlerHistory in Framework.StatsHistory) + { if (handlerHistory.Value.Count == 0) continue; ImGui.SameLine(); ImGui.Text($"{handlerHistory.Key}"); @@ -120,13 +142,15 @@ namespace Dalamud.Interface { ImGui.NextColumn(); ImGui.Separator(); } + ImGui.Columns(0); } ImGui.EndTabItem(); } - if (ImGui.BeginTabItem("Hooks")) { + if (ImGui.BeginTabItem("Hooks")) + { ImGui.Columns(3); ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 280); ImGui.SetColumnWidth(1, 180f); @@ -135,7 +159,7 @@ namespace Dalamud.Interface { ImGui.SameLine(); ImGui.Text(" "); ImGui.SameLine(); - ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref showDalamudHooks); + ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref this.showDalamudHooks); ImGui.NextColumn(); ImGui.Text("Address"); ImGui.NextColumn(); @@ -144,34 +168,43 @@ namespace Dalamud.Interface { ImGui.Separator(); ImGui.Separator(); - foreach (var trackedHook in HookInfo.TrackedHooks) { - try { + foreach (var trackedHook in HookInfo.TrackedHooks) + { + try + { if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) continue; ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}"); ImGui.TextDisabled(trackedHook.Assembly.FullName); ImGui.NextColumn(); - if (!trackedHook.Hook.IsDisposed) { + if (!trackedHook.Hook.IsDisposed) + { ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}"); if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}"); var processMemoryOffset = trackedHook.InProcessMemory; - if (processMemoryOffset.HasValue) { + if (processMemoryOffset.HasValue) + { ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}"); if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}"); } - } + ImGui.NextColumn(); - if (trackedHook.Hook.IsDisposed) { + if (trackedHook.Hook.IsDisposed) + { ImGui.Text("Disposed"); - } else { + } + else + { ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled"); } ImGui.NextColumn(); - } catch (Exception ex) { + } + catch (Exception ex) + { ImGui.Text(ex.Message); ImGui.NextColumn(); while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn(); @@ -179,11 +212,12 @@ namespace Dalamud.Interface { ImGui.Separator(); } - + ImGui.Columns(); } - if (ImGui.IsWindowAppearing()) { + if (ImGui.IsWindowAppearing()) + { HookInfo.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed); } diff --git a/Dalamud/Interface/DalamudSettingsWindow.cs b/Dalamud/Interface/DalamudSettingsWindow.cs index 8cc68ec80..544e14b67 100644 --- a/Dalamud/Interface/DalamudSettingsWindow.cs +++ b/Dalamud/Interface/DalamudSettingsWindow.cs @@ -4,7 +4,7 @@ using System.Globalization; using System.Linq; using System.Numerics; using System.Threading.Tasks; -using System.Windows.Forms.VisualStyles; + using CheapLoc; using Dalamud.Configuration; using Dalamud.Game.Text; @@ -15,10 +15,55 @@ using Serilog; namespace Dalamud.Interface { - internal class DalamudSettingsWindow : Window { + /// + /// The window that allows for general configuration of Dalamud itself. + /// + internal class DalamudSettingsWindow : Window + { + private const float MinScale = 0.3f; + private const float MaxScale = 2.0f; + private readonly Dalamud dalamud; - public DalamudSettingsWindow(Dalamud dalamud) + private string[] languages; + private string[] locLanguages; + private int langIndex; + + private Vector4 hintTextColor = ImGuiColors.DalamudGrey; + private Vector4 warnTextColor = ImGuiColors.DalamudRed; + + private XivChatType dalamudMessagesChatType; + + private bool doCfTaskBarFlash; + private bool doCfChatMessage; + + private float globalUiScale; + private bool doToggleUiHide; + private bool doToggleUiHideDuringCutscenes; + private bool doToggleUiHideDuringGpose; + private bool doDocking; + private bool doViewport; + private bool doGamepad; + private List thirdRepoList; + + private bool printPluginsWelcomeMsg; + private bool autoUpdatePlugins; + private bool doButtonsSystemMenu; + + private string thirdRepoTempUrl = string.Empty; + private string thirdRepoAddError = string.Empty; + + #region Experimental + + private bool doPluginTest; + + #endregion + + /// + /// Initializes a new instance of the class. + /// + /// The Dalamud Instance. + public DalamudSettingsWindow(Dalamud dalamud) : base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse) { this.dalamud = dalamud; @@ -71,20 +116,21 @@ namespace Dalamud.Interface try { - List locLanguagesList = new List(); + var locLanguagesList = new List(); string locLanguage; foreach (var language in this.languages) { if (language != "ko") { locLanguage = CultureInfo.GetCultureInfo(language).NativeName; - locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage.Substring(1)); + locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage[1..]); } else { locLanguagesList.Add("Korean"); } } + this.locLanguages = locLanguagesList.ToArray(); } catch (Exception) @@ -93,6 +139,7 @@ namespace Dalamud.Interface } } + /// public override void OnOpen() { base.OnOpen(); @@ -102,6 +149,7 @@ namespace Dalamud.Interface Log.Information("OnOpen end"); } + /// public override void OnClose() { base.OnClose(); @@ -114,64 +162,36 @@ namespace Dalamud.Interface Log.Information("OnClose end"); } - private string[] languages; - private string[] locLanguages; - private int langIndex; - - private Vector4 hintTextColor = ImGuiColors.DalamudGrey; - private Vector4 warnTextColor = ImGuiColors.DalamudRed; - - private XivChatType dalamudMessagesChatType; - - private bool doCfTaskBarFlash; - private bool doCfChatMessage; - - private const float MinScale = 0.3f; - private const float MaxScale = 2.0f; - private float globalUiScale; - private bool doToggleUiHide; - private bool doToggleUiHideDuringCutscenes; - private bool doToggleUiHideDuringGpose; - private bool doDocking; - private bool doViewport; - private bool doGamepad; - private List thirdRepoList; - - private bool printPluginsWelcomeMsg; - private bool autoUpdatePlugins; - private bool doButtonsSystemMenu; - - private string thirdRepoTempUrl = string.Empty; - private string thirdRepoAddError = string.Empty; - - #region Experimental - - private bool doPluginTest; - - #endregion - - public override void Draw() { + /// + public override void Draw() + { var windowSize = ImGui.GetWindowSize(); ImGui.BeginChild("scrolling", new Vector2(windowSize.X - 5 - (5 * ImGui.GetIO().FontGlobalScale), windowSize.Y - 35 - (35 * ImGui.GetIO().FontGlobalScale)), false, ImGuiWindowFlags.HorizontalScrollbar); - if (ImGui.BeginTabBar("SetTabBar")) { - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General"))) { - ImGui.Text(Loc.Localize("DalamudSettingsLanguage","Language")); - ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, - this.locLanguages.Length); + if (ImGui.BeginTabBar("SetTabBar")) + { + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General"))) + { + ImGui.Text(Loc.Localize("DalamudSettingsLanguage", "Language")); + ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length); ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in.")); ImGui.Dummy(new Vector2(5f, 5f) * ImGui.GetIO().FontGlobalScale); ImGui.Text(Loc.Localize("DalamudSettingsChannel", "General Chat Channel")); - if (ImGui.BeginCombo("##XlChatTypeCombo", this.dalamudMessagesChatType.ToString())) { - foreach (var type in Enum.GetValues(typeof(XivChatType)).Cast()) { - if (ImGui.Selectable(type.ToString(), type == this.dalamudMessagesChatType)) { + if (ImGui.BeginCombo("##XlChatTypeCombo", this.dalamudMessagesChatType.ToString())) + { + foreach (var type in Enum.GetValues(typeof(XivChatType)).Cast()) + { + if (ImGui.Selectable(type.ToString(), type == this.dalamudMessagesChatType)) + { this.dalamudMessagesChatType = type; } } + ImGui.EndCombo(); } + ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages.")); ImGui.Dummy(new Vector2(5f, 5f) * ImGui.GetIO().FontGlobalScale); @@ -194,12 +214,14 @@ namespace Dalamud.Interface ImGui.EndTabItem(); } - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel"))) { + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel"))) + { ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global UI Scale")); ImGui.SameLine(); ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); - if (ImGui.Button("Reset")) { + if (ImGui.Button("Reset")) + { this.globalUiScale = 1.0f; ImGui.GetIO().FontGlobalScale = this.globalUiScale; } @@ -236,7 +258,8 @@ namespace Dalamud.Interface ImGui.EndTabItem(); } - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental"))) { + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental"))) + { ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest); ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for plugins.")); ImGui.TextColored(this.warnTextColor, Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may not have been vetted before being published. Please only enable this if you are aware of the risks.")); @@ -258,8 +281,8 @@ namespace Dalamud.Interface ImGui.Dummy(new Vector2(5f, 5f) * ImGui.GetIO().FontGlobalScale); ImGui.Columns(4); - ImGui.SetColumnWidth(0, 18 + 5 * ImGui.GetIO().FontGlobalScale); - ImGui.SetColumnWidth(1, ImGui.GetWindowWidth() - (18 + 16 + 14) - (5 + 45 + 26) * ImGui.GetIO().FontGlobalScale); + ImGui.SetColumnWidth(0, 18 + (5 * ImGui.GetIO().FontGlobalScale)); + ImGui.SetColumnWidth(1, ImGui.GetWindowWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGui.GetIO().FontGlobalScale)); ImGui.SetColumnWidth(2, 16 + (45 * ImGui.GetIO().FontGlobalScale)); ImGui.SetColumnWidth(3, 14 + (26 * ImGui.GetIO().FontGlobalScale)); @@ -271,7 +294,7 @@ namespace Dalamud.Interface ImGui.NextColumn(); ImGui.Text("Enabled"); ImGui.NextColumn(); - ImGui.Text(""); + ImGui.Text(string.Empty); ImGui.NextColumn(); ImGui.Separator(); @@ -287,7 +310,8 @@ namespace Dalamud.Interface ThirdRepoSetting toRemove = null; var repoNumber = 1; - foreach (var thirdRepoSetting in this.thirdRepoList) { + foreach (var thirdRepoSetting in this.thirdRepoList) + { var isEnabled = thirdRepoSetting.IsEnabled; ImGui.PushID($"thirdRepo_{thirdRepoSetting.Url}"); @@ -299,14 +323,16 @@ namespace Dalamud.Interface ImGui.TextWrapped(thirdRepoSetting.Url); ImGui.NextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - 12 * ImGui.GetIO().FontGlobalScale); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGui.GetIO().FontGlobalScale)); ImGui.Checkbox("##thirdRepoCheck", ref isEnabled); ImGui.NextColumn(); ImGui.PushFont(InterfaceManager.IconFont); - if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString())) { + if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString())) + { toRemove = thirdRepoSetting; } + ImGui.PopFont(); ImGui.NextColumn(); ImGui.Separator(); @@ -316,7 +342,8 @@ namespace Dalamud.Interface repoNumber++; } - if (toRemove != null) { + if (toRemove != null) + { this.thirdRepoList.Remove(toRemove); } @@ -328,25 +355,32 @@ namespace Dalamud.Interface ImGui.NextColumn(); ImGui.NextColumn(); ImGui.PushFont(InterfaceManager.IconFont); - if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGui.Button(FontAwesomeIcon.Plus.ToIconString())) { - if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) { + if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGui.Button(FontAwesomeIcon.Plus.ToIconString())) + { + if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) + { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); - } else { - this.thirdRepoList.Add(new ThirdRepoSetting { + } + else + { + this.thirdRepoList.Add(new ThirdRepoSetting + { Url = this.thirdRepoTempUrl, - IsEnabled = true + IsEnabled = true, }); this.thirdRepoTempUrl = string.Empty; } } + ImGui.PopFont(); ImGui.Columns(1); ImGui.EndTabItem(); - if (!string.IsNullOrEmpty(this.thirdRepoAddError)) { + if (!string.IsNullOrEmpty(this.thirdRepoAddError)) + { ImGui.TextColored(new Vector4(1, 0, 0, 1), this.thirdRepoAddError); } } @@ -356,19 +390,22 @@ namespace Dalamud.Interface ImGui.EndChild(); - if (ImGui.Button(Loc.Localize("Save", "Save"))) { - Save(); + if (ImGui.Button(Loc.Localize("Save", "Save"))) + { + this.Save(); } ImGui.SameLine(); - if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) { - Save(); + if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) + { + this.Save(); this.IsOpen = false; } } - private void Save() { + private void Save() + { this.dalamud.LocalizationManager.SetupWithLangCode(this.languages[this.langIndex]); this.dalamud.Configuration.LanguageOverride = this.languages[this.langIndex]; diff --git a/Dalamud/Interface/FontAwesomeIcon.cs b/Dalamud/Interface/FontAwesomeIcon.cs index c433715b3..a8b93e9e7 100644 --- a/Dalamud/Interface/FontAwesomeIcon.cs +++ b/Dalamud/Interface/FontAwesomeIcon.cs @@ -1,1430 +1,7076 @@ -//Font-Awesome - Version 5.0.9 -using System.Collections.Generic; -#pragma warning disable 1591 +// Font-Awesome - Version 5.0.9 namespace Dalamud.Interface { + /// + /// Font Awesome unicode characters for use with the font. + /// public enum FontAwesomeIcon { + /// + /// No icon. + /// None = 0, - _500Px = 0xf26e, - AccessibleIcon = 0xf368, - Accusoft = 0xf369, - AcquisitionsIncorporated = 0xf6af, - Ad = 0xf641, - AddressBook = 0xf2b9, - AddressCard = 0xf2bb, - Adjust = 0xf042, - Adn = 0xf170, - Adobe = 0xf778, - Adversal = 0xf36a, - Affiliatetheme = 0xf36b, - Airbnb = 0xf834, - AirFreshener = 0xf5d0, - Algolia = 0xf36c, - AlignCenter = 0xf037, - AlignJustify = 0xf039, - AlignLeft = 0xf036, - AlignRight = 0xf038, - Alipay = 0xf642, - Allergies = 0xf461, - Amazon = 0xf270, - AmazonPay = 0xf42c, - Ambulance = 0xf0f9, - AmericanSignLanguageInterpreting = 0xf2a3, - Amilia = 0xf36d, - Anchor = 0xf13d, - Android = 0xf17b, - Angellist = 0xf209, - AngleDoubleDown = 0xf103, - AngleDoubleLeft = 0xf100, - AngleDoubleRight = 0xf101, - AngleDoubleUp = 0xf102, - AngleDown = 0xf107, - AngleLeft = 0xf104, - AngleRight = 0xf105, - AngleUp = 0xf106, - Angry = 0xf556, - Angrycreative = 0xf36e, - Angular = 0xf420, - Ankh = 0xf644, - Apper = 0xf371, - Apple = 0xf179, - AppleAlt = 0xf5d1, - ApplePay = 0xf415, - AppStore = 0xf36f, - AppStoreIos = 0xf370, - Archive = 0xf187, - Archway = 0xf557, - ArrowAltCircleDown = 0xf358, - ArrowAltCircleLeft = 0xf359, - ArrowAltCircleRight = 0xf35a, - ArrowAltCircleUp = 0xf35b, - ArrowCircleDown = 0xf0ab, - ArrowCircleLeft = 0xf0a8, - ArrowCircleRight = 0xf0a9, - ArrowCircleUp = 0xf0aa, - ArrowDown = 0xf063, - ArrowLeft = 0xf060, - ArrowRight = 0xf061, - ArrowsAlt = 0xf0b2, - ArrowsAltH = 0xf337, - ArrowsAltV = 0xf338, - ArrowUp = 0xf062, - Artstation = 0xf77a, - AssistiveListeningSystems = 0xf2a2, - Asterisk = 0xf069, - Asymmetrik = 0xf372, - At = 0xf1fa, - Atlas = 0xf558, - Atlassian = 0xf77b, - Atom = 0xf5d2, - Audible = 0xf373, - AudioDescription = 0xf29e, - Autoprefixer = 0xf41c, - Avianex = 0xf374, - Aviato = 0xf421, - Award = 0xf559, - Aws = 0xf375, - Baby = 0xf77c, - BabyCarriage = 0xf77d, - Backspace = 0xf55a, - Backward = 0xf04a, - Bacon = 0xf7e5, - Bahai = 0xf666, - BalanceScale = 0xf24e, - BalanceScaleLeft = 0xf515, - BalanceScaleRight = 0xf516, - Ban = 0xf05e, - BandAid = 0xf462, - Bandcamp = 0xf2d5, - Barcode = 0xf02a, - Bars = 0xf0c9, - BaseballBall = 0xf433, - BasketballBall = 0xf434, - Bath = 0xf2cd, - BatteryEmpty = 0xf244, - BatteryFull = 0xf240, - BatteryHalf = 0xf242, - BatteryQuarter = 0xf243, - BatteryThreeQuarters = 0xf241, - BattleNet = 0xf835, - Bed = 0xf236, - Beer = 0xf0fc, - Behance = 0xf1b4, - BehanceSquare = 0xf1b5, - Bell = 0xf0f3, - BellSlash = 0xf1f6, - BezierCurve = 0xf55b, - Bible = 0xf647, - Bicycle = 0xf206, - Biking = 0xf84a, - Bimobject = 0xf378, - Binoculars = 0xf1e5, - Biohazard = 0xf780, - BirthdayCake = 0xf1fd, - Bitbucket = 0xf171, - Bitcoin = 0xf379, - Bity = 0xf37a, - Blackberry = 0xf37b, - BlackTie = 0xf27e, - Blender = 0xf517, - BlenderPhone = 0xf6b6, - Blind = 0xf29d, - Blog = 0xf781, - Blogger = 0xf37c, - BloggerB = 0xf37d, - Bluetooth = 0xf293, - BluetoothB = 0xf294, - Bold = 0xf032, - Bolt = 0xf0e7, - Bomb = 0xf1e2, - Bone = 0xf5d7, - Bong = 0xf55c, - Book = 0xf02d, - BookDead = 0xf6b7, - Bookmark = 0xf02e, - BookMedical = 0xf7e6, - BookOpen = 0xf518, - BookReader = 0xf5da, - Bootstrap = 0xf836, - BorderAll = 0xf84c, - BorderNone = 0xf850, - BorderStyle = 0xf853, - BowlingBall = 0xf436, - Box = 0xf466, - Boxes = 0xf468, - BoxOpen = 0xf49e, - Braille = 0xf2a1, - Brain = 0xf5dc, - BreadSlice = 0xf7ec, - Briefcase = 0xf0b1, - BriefcaseMedical = 0xf469, - BroadcastTower = 0xf519, - Broom = 0xf51a, - Brush = 0xf55d, - Btc = 0xf15a, - Buffer = 0xf837, - Bug = 0xf188, - Building = 0xf1ad, - Bullhorn = 0xf0a1, - Bullseye = 0xf140, - Burn = 0xf46a, - Buromobelexperte = 0xf37f, - Bus = 0xf207, - BusAlt = 0xf55e, - BusinessTime = 0xf64a, - BuyNLarge = 0xf8a6, - Buysellads = 0xf20d, - Calculator = 0xf1ec, - Calendar = 0xf133, - CalendarAlt = 0xf073, - CalendarCheck = 0xf274, - CalendarDay = 0xf783, - CalendarMinus = 0xf272, - CalendarPlus = 0xf271, - CalendarTimes = 0xf273, - CalendarWeek = 0xf784, - Camera = 0xf030, - CameraRetro = 0xf083, - Campground = 0xf6bb, - CanadianMapleLeaf = 0xf785, - CandyCane = 0xf786, - Cannabis = 0xf55f, - Capsules = 0xf46b, - Car = 0xf1b9, - CarAlt = 0xf5de, - Caravan = 0xf8ff, - CarBattery = 0xf5df, - CarCrash = 0xf5e1, - CaretDown = 0xf0d7, - CaretLeft = 0xf0d9, - CaretRight = 0xf0da, - CaretSquareDown = 0xf150, - CaretSquareLeft = 0xf191, - CaretSquareRight = 0xf152, - CaretSquareUp = 0xf151, - CaretUp = 0xf0d8, - Carrot = 0xf787, - CarSide = 0xf5e4, - CartArrowDown = 0xf218, - CartPlus = 0xf217, - CashRegister = 0xf788, - Cat = 0xf6be, - CcAmazonPay = 0xf42d, - CcAmex = 0xf1f3, - CcApplePay = 0xf416, - CcDinersClub = 0xf24c, - CcDiscover = 0xf1f2, - CcJcb = 0xf24b, - CcMastercard = 0xf1f1, - CcPaypal = 0xf1f4, - CcStripe = 0xf1f5, - CcVisa = 0xf1f0, - Centercode = 0xf380, - Centos = 0xf789, - Certificate = 0xf0a3, - Chair = 0xf6c0, - Chalkboard = 0xf51b, - ChalkboardTeacher = 0xf51c, - ChargingStation = 0xf5e7, - ChartArea = 0xf1fe, - ChartBar = 0xf080, - ChartLine = 0xf201, - ChartPie = 0xf200, - Check = 0xf00c, - CheckCircle = 0xf058, - CheckDouble = 0xf560, - CheckSquare = 0xf14a, - Cheese = 0xf7ef, - Chess = 0xf439, - ChessBishop = 0xf43a, - ChessBoard = 0xf43c, - ChessKing = 0xf43f, - ChessKnight = 0xf441, - ChessPawn = 0xf443, - ChessQueen = 0xf445, - ChessRook = 0xf447, - ChevronCircleDown = 0xf13a, - ChevronCircleLeft = 0xf137, - ChevronCircleRight = 0xf138, - ChevronCircleUp = 0xf139, - ChevronDown = 0xf078, - ChevronLeft = 0xf053, - ChevronRight = 0xf054, - ChevronUp = 0xf077, - Child = 0xf1ae, - Chrome = 0xf268, - Chromecast = 0xf838, - Church = 0xf51d, - Circle = 0xf111, - CircleNotch = 0xf1ce, - City = 0xf64f, - ClinicMedical = 0xf7f2, - Clipboard = 0xf328, - ClipboardCheck = 0xf46c, - ClipboardList = 0xf46d, - Clock = 0xf017, - Clone = 0xf24d, - ClosedCaptioning = 0xf20a, - Cloud = 0xf0c2, - CloudDownloadAlt = 0xf381, - CloudMeatball = 0xf73b, - CloudMoon = 0xf6c3, - CloudMoonRain = 0xf73c, - CloudRain = 0xf73d, - Cloudscale = 0xf383, - CloudShowersHeavy = 0xf740, - Cloudsmith = 0xf384, - CloudSun = 0xf6c4, - CloudSunRain = 0xf743, - CloudUploadAlt = 0xf382, - Cloudversify = 0xf385, - Cocktail = 0xf561, - Code = 0xf121, - CodeBranch = 0xf126, - Codepen = 0xf1cb, - Codiepie = 0xf284, - Coffee = 0xf0f4, - Cog = 0xf013, - Cogs = 0xf085, - Coins = 0xf51e, - Columns = 0xf0db, - Comment = 0xf075, - CommentAlt = 0xf27a, - CommentDollar = 0xf651, - CommentDots = 0xf4ad, - CommentMedical = 0xf7f5, - Comments = 0xf086, - CommentsDollar = 0xf653, - CommentSlash = 0xf4b3, - CompactDisc = 0xf51f, - Compass = 0xf14e, - Compress = 0xf066, - CompressAlt = 0xf422, - CompressArrowsAlt = 0xf78c, - ConciergeBell = 0xf562, - Confluence = 0xf78d, - Connectdevelop = 0xf20e, - Contao = 0xf26d, - Cookie = 0xf563, - CookieBite = 0xf564, - Copy = 0xf0c5, - Copyright = 0xf1f9, - CottonBureau = 0xf89e, - Couch = 0xf4b8, - Cpanel = 0xf388, - CreativeCommons = 0xf25e, - CreativeCommonsBy = 0xf4e7, - CreativeCommonsNc = 0xf4e8, - CreativeCommonsNcEu = 0xf4e9, - CreativeCommonsNcJp = 0xf4ea, - CreativeCommonsNd = 0xf4eb, - CreativeCommonsPd = 0xf4ec, - CreativeCommonsPdAlt = 0xf4ed, - CreativeCommonsRemix = 0xf4ee, - CreativeCommonsSa = 0xf4ef, - CreativeCommonsSampling = 0xf4f0, - CreativeCommonsSamplingPlus = 0xf4f1, - CreativeCommonsShare = 0xf4f2, - CreativeCommonsZero = 0xf4f3, - CreditCard = 0xf09d, - CriticalRole = 0xf6c9, - Crop = 0xf125, - CropAlt = 0xf565, - Cross = 0xf654, - Crosshairs = 0xf05b, - Crow = 0xf520, - Crown = 0xf521, - Crutch = 0xf7f7, - Css3 = 0xf13c, - Css3Alt = 0xf38b, - Cube = 0xf1b2, - Cubes = 0xf1b3, - Cut = 0xf0c4, - Cuttlefish = 0xf38c, - Dailymotion = 0xf952, - DAndD = 0xf38d, - DAndDBeyond = 0xf6ca, - Dashcube = 0xf210, - Database = 0xf1c0, - Deaf = 0xf2a4, - Delicious = 0xf1a5, - Democrat = 0xf747, - Deploydog = 0xf38e, - Deskpro = 0xf38f, - Desktop = 0xf108, - Dev = 0xf6cc, - Deviantart = 0xf1bd, - Dharmachakra = 0xf655, - Dhl = 0xf790, - Diagnoses = 0xf470, - Diaspora = 0xf791, - Dice = 0xf522, - DiceD20 = 0xf6cf, - DiceD6 = 0xf6d1, - DiceFive = 0xf523, - DiceFour = 0xf524, - DiceOne = 0xf525, - DiceSix = 0xf526, - DiceThree = 0xf527, - DiceTwo = 0xf528, - Digg = 0xf1a6, - DigitalOcean = 0xf391, - DigitalTachograph = 0xf566, - Directions = 0xf5eb, - Discord = 0xf392, - Discourse = 0xf393, - Divide = 0xf529, - Dizzy = 0xf567, - Dna = 0xf471, - Dochub = 0xf394, - Docker = 0xf395, - Dog = 0xf6d3, - DollarSign = 0xf155, - Dolly = 0xf472, - DollyFlatbed = 0xf474, - Donate = 0xf4b9, - DoorClosed = 0xf52a, - DoorOpen = 0xf52b, - DotCircle = 0xf192, - Dove = 0xf4ba, - Download = 0xf019, - Draft2digital = 0xf396, - DraftingCompass = 0xf568, - Dragon = 0xf6d5, - DrawPolygon = 0xf5ee, - Dribbble = 0xf17d, - DribbbleSquare = 0xf397, - Dropbox = 0xf16b, - Drum = 0xf569, - DrumSteelpan = 0xf56a, - DrumstickBite = 0xf6d7, - Drupal = 0xf1a9, - Dumbbell = 0xf44b, - Dumpster = 0xf793, - DumpsterFire = 0xf794, - Dungeon = 0xf6d9, - Dyalog = 0xf399, - Earlybirds = 0xf39a, - Ebay = 0xf4f4, - Edge = 0xf282, - Edit = 0xf044, - Egg = 0xf7fb, - Eject = 0xf052, - Elementor = 0xf430, - EllipsisH = 0xf141, - EllipsisV = 0xf142, - Ello = 0xf5f1, - Ember = 0xf423, - Empire = 0xf1d1, - Envelope = 0xf0e0, - EnvelopeOpen = 0xf2b6, - EnvelopeOpenText = 0xf658, - EnvelopeSquare = 0xf199, - Envira = 0xf299, - Equals = 0xf52c, - Eraser = 0xf12d, - Erlang = 0xf39d, - Ethereum = 0xf42e, - Ethernet = 0xf796, - Etsy = 0xf2d7, - EuroSign = 0xf153, - Evernote = 0xf839, - ExchangeAlt = 0xf362, - Exclamation = 0xf12a, - ExclamationCircle = 0xf06a, - ExclamationTriangle = 0xf071, - Expand = 0xf065, - ExpandAlt = 0xf424, - ExpandArrowsAlt = 0xf31e, - Expeditedssl = 0xf23e, - ExternalLinkAlt = 0xf35d, - ExternalLinkSquareAlt = 0xf360, - Eye = 0xf06e, - EyeDropper = 0xf1fb, - EyeSlash = 0xf070, - Facebook = 0xf09a, - FacebookF = 0xf39e, - FacebookMessenger = 0xf39f, - FacebookSquare = 0xf082, - Fan = 0xf863, - FantasyFlightGames = 0xf6dc, - FastBackward = 0xf049, - FastForward = 0xf050, - Fax = 0xf1ac, - Feather = 0xf52d, - FeatherAlt = 0xf56b, - Fedex = 0xf797, - Fedora = 0xf798, - Female = 0xf182, - FighterJet = 0xf0fb, - Figma = 0xf799, - File = 0xf15b, - FileAlt = 0xf15c, - FileArchive = 0xf1c6, - FileAudio = 0xf1c7, - FileCode = 0xf1c9, - FileContract = 0xf56c, - FileCsv = 0xf6dd, - FileDownload = 0xf56d, - FileExcel = 0xf1c3, - FileExport = 0xf56e, - FileImage = 0xf1c5, - FileImport = 0xf56f, - FileInvoice = 0xf570, - FileInvoiceDollar = 0xf571, - FileMedical = 0xf477, - FileMedicalAlt = 0xf478, - FilePdf = 0xf1c1, - FilePowerpoint = 0xf1c4, - FilePrescription = 0xf572, - FileSignature = 0xf573, - FileUpload = 0xf574, - FileVideo = 0xf1c8, - FileWord = 0xf1c2, - Fill = 0xf575, - FillDrip = 0xf576, - Film = 0xf008, - Filter = 0xf0b0, - Fingerprint = 0xf577, - Fire = 0xf06d, - FireAlt = 0xf7e4, - FireExtinguisher = 0xf134, - Firefox = 0xf269, - FirefoxBrowser = 0xf907, - FirstAid = 0xf479, - Firstdraft = 0xf3a1, - FirstOrder = 0xf2b0, - FirstOrderAlt = 0xf50a, - Fish = 0xf578, - FistRaised = 0xf6de, - Flag = 0xf024, - FlagCheckered = 0xf11e, - FlagUsa = 0xf74d, - Flask = 0xf0c3, - Flickr = 0xf16e, - Flipboard = 0xf44d, - Flushed = 0xf579, - Fly = 0xf417, - Folder = 0xf07b, - FolderMinus = 0xf65d, - FolderOpen = 0xf07c, - FolderPlus = 0xf65e, - Font = 0xf031, - FontAwesome = 0xf2b4, - FontAwesomeAlt = 0xf35c, - FontAwesomeFlag = 0xf425, - FontAwesomeLogoFull = 0xf4e6, - Fonticons = 0xf280, - FonticonsFi = 0xf3a2, - FootballBall = 0xf44e, - FortAwesome = 0xf286, - FortAwesomeAlt = 0xf3a3, - Forumbee = 0xf211, - Forward = 0xf04e, - Foursquare = 0xf180, - Freebsd = 0xf3a4, - FreeCodeCamp = 0xf2c5, - Frog = 0xf52e, - Frown = 0xf119, - FrownOpen = 0xf57a, - Fulcrum = 0xf50b, - FunnelDollar = 0xf662, - Futbol = 0xf1e3, - GalacticRepublic = 0xf50c, - GalacticSenate = 0xf50d, - Gamepad = 0xf11b, - GasPump = 0xf52f, - Gavel = 0xf0e3, - Gem = 0xf3a5, - Genderless = 0xf22d, - GetPocket = 0xf265, - Gg = 0xf260, - GgCircle = 0xf261, - Ghost = 0xf6e2, - Gift = 0xf06b, - Gifts = 0xf79c, - Git = 0xf1d3, - GitAlt = 0xf841, - Github = 0xf09b, - GithubAlt = 0xf113, - GithubSquare = 0xf092, - Gitkraken = 0xf3a6, - Gitlab = 0xf296, - GitSquare = 0xf1d2, - Gitter = 0xf426, - GlassCheers = 0xf79f, - Glasses = 0xf530, - GlassMartini = 0xf000, - GlassMartiniAlt = 0xf57b, - GlassWhiskey = 0xf7a0, - Glide = 0xf2a5, - GlideG = 0xf2a6, - Globe = 0xf0ac, - GlobeAfrica = 0xf57c, - GlobeAmericas = 0xf57d, - GlobeAsia = 0xf57e, - GlobeEurope = 0xf7a2, - Gofore = 0xf3a7, - GolfBall = 0xf450, - Goodreads = 0xf3a8, - GoodreadsG = 0xf3a9, - Google = 0xf1a0, - GoogleDrive = 0xf3aa, - GooglePlay = 0xf3ab, - GooglePlus = 0xf2b3, - GooglePlusG = 0xf0d5, - GooglePlusSquare = 0xf0d4, - GoogleWallet = 0xf1ee, - Gopuram = 0xf664, - GraduationCap = 0xf19d, - Gratipay = 0xf184, - Grav = 0xf2d6, - GreaterThan = 0xf531, - GreaterThanEqual = 0xf532, - Grimace = 0xf57f, - Grin = 0xf580, - GrinAlt = 0xf581, - GrinBeam = 0xf582, - GrinBeamSweat = 0xf583, - GrinHearts = 0xf584, - GrinSquint = 0xf585, - GrinSquintTears = 0xf586, - GrinStars = 0xf587, - GrinTears = 0xf588, - GrinTongue = 0xf589, - GrinTongueSquint = 0xf58a, - GrinTongueWink = 0xf58b, - GrinWink = 0xf58c, - Gripfire = 0xf3ac, - GripHorizontal = 0xf58d, - GripLines = 0xf7a4, - GripLinesVertical = 0xf7a5, - GripVertical = 0xf58e, - Grunt = 0xf3ad, - Guitar = 0xf7a6, - Gulp = 0xf3ae, - HackerNews = 0xf1d4, - HackerNewsSquare = 0xf3af, - Hackerrank = 0xf5f7, - Hamburger = 0xf805, - Hammer = 0xf6e3, - Hamsa = 0xf665, - HandHolding = 0xf4bd, - HandHoldingHeart = 0xf4be, - HandHoldingUsd = 0xf4c0, - HandLizard = 0xf258, - HandMiddleFinger = 0xf806, - HandPaper = 0xf256, - HandPeace = 0xf25b, - HandPointDown = 0xf0a7, - HandPointer = 0xf25a, - HandPointLeft = 0xf0a5, - HandPointRight = 0xf0a4, - HandPointUp = 0xf0a6, - HandRock = 0xf255, - Hands = 0xf4c2, - HandScissors = 0xf257, - Handshake = 0xf2b5, - HandsHelping = 0xf4c4, - HandSpock = 0xf259, - Hanukiah = 0xf6e6, - HardHat = 0xf807, - Hashtag = 0xf292, - HatCowboy = 0xf8c0, - HatCowboySide = 0xf8c1, - HatWizard = 0xf6e8, - Hdd = 0xf0a0, - Heading = 0xf1dc, - Headphones = 0xf025, - HeadphonesAlt = 0xf58f, - Headset = 0xf590, - Heart = 0xf004, - Heartbeat = 0xf21e, - HeartBroken = 0xf7a9, - Helicopter = 0xf533, - Highlighter = 0xf591, - Hiking = 0xf6ec, - Hippo = 0xf6ed, - Hips = 0xf452, - HireAHelper = 0xf3b0, - History = 0xf1da, - HockeyPuck = 0xf453, - HollyBerry = 0xf7aa, - Home = 0xf015, - Hooli = 0xf427, - Hornbill = 0xf592, - Horse = 0xf6f0, - HorseHead = 0xf7ab, - Hospital = 0xf0f8, - HospitalAlt = 0xf47d, - HospitalSymbol = 0xf47e, - Hotdog = 0xf80f, - Hotel = 0xf594, - Hotjar = 0xf3b1, - HotTub = 0xf593, - Hourglass = 0xf254, - HourglassEnd = 0xf253, - HourglassHalf = 0xf252, - HourglassStart = 0xf251, - HouseDamage = 0xf6f1, - Houzz = 0xf27c, - Hryvnia = 0xf6f2, - HSquare = 0xf0fd, - Html5 = 0xf13b, - Hubspot = 0xf3b2, - IceCream = 0xf810, - Icicles = 0xf7ad, - Icons = 0xf86d, - ICursor = 0xf246, - IdBadge = 0xf2c1, - IdCard = 0xf2c2, - IdCardAlt = 0xf47f, - Ideal = 0xf913, - Igloo = 0xf7ae, - Image = 0xf03e, - Images = 0xf302, - Imdb = 0xf2d8, - Inbox = 0xf01c, - Indent = 0xf03c, - Industry = 0xf275, - Infinity = 0xf534, - Info = 0xf129, - InfoCircle = 0xf05a, - Instagram = 0xf16d, - InstagramSquare = 0xf955, - Intercom = 0xf7af, - InternetExplorer = 0xf26b, - Invision = 0xf7b0, - Ioxhost = 0xf208, - Italic = 0xf033, - ItchIo = 0xf83a, - Itunes = 0xf3b4, - ItunesNote = 0xf3b5, - Java = 0xf4e4, - Jedi = 0xf669, - JediOrder = 0xf50e, - Jenkins = 0xf3b6, - Jira = 0xf7b1, - Joget = 0xf3b7, - Joint = 0xf595, - Joomla = 0xf1aa, - JournalWhills = 0xf66a, - Js = 0xf3b8, - Jsfiddle = 0xf1cc, - JsSquare = 0xf3b9, - Kaaba = 0xf66b, - Kaggle = 0xf5fa, - Key = 0xf084, - Keybase = 0xf4f5, - Keyboard = 0xf11c, - Keycdn = 0xf3ba, - Khanda = 0xf66d, - Kickstarter = 0xf3bb, - KickstarterK = 0xf3bc, - Kiss = 0xf596, - KissBeam = 0xf597, - KissWinkHeart = 0xf598, - KiwiBird = 0xf535, - Korvue = 0xf42f, - Landmark = 0xf66f, - Language = 0xf1ab, - Laptop = 0xf109, - LaptopCode = 0xf5fc, - LaptopMedical = 0xf812, - Laravel = 0xf3bd, - Lastfm = 0xf202, - LastfmSquare = 0xf203, - Laugh = 0xf599, - LaughBeam = 0xf59a, - LaughSquint = 0xf59b, - LaughWink = 0xf59c, - LayerGroup = 0xf5fd, - Leaf = 0xf06c, - Leanpub = 0xf212, - Lemon = 0xf094, - Less = 0xf41d, - LessThan = 0xf536, - LessThanEqual = 0xf537, - LevelDownAlt = 0xf3be, - LevelUpAlt = 0xf3bf, - LifeRing = 0xf1cd, - Lightbulb = 0xf0eb, - Line = 0xf3c0, - Link = 0xf0c1, - Linkedin = 0xf08c, - LinkedinIn = 0xf0e1, - Linode = 0xf2b8, - Linux = 0xf17c, - LiraSign = 0xf195, - List = 0xf03a, - ListAlt = 0xf022, - ListOl = 0xf0cb, - ListUl = 0xf0ca, - LocationArrow = 0xf124, - Lock = 0xf023, - LockOpen = 0xf3c1, - LongArrowAltDown = 0xf309, - LongArrowAltLeft = 0xf30a, - LongArrowAltRight = 0xf30b, - LongArrowAltUp = 0xf30c, - LowVision = 0xf2a8, - LuggageCart = 0xf59d, - Lyft = 0xf3c3, - Magento = 0xf3c4, - Magic = 0xf0d0, - Magnet = 0xf076, - MailBulk = 0xf674, - Mailchimp = 0xf59e, - Male = 0xf183, - Mandalorian = 0xf50f, - Map = 0xf279, - MapMarked = 0xf59f, - MapMarkedAlt = 0xf5a0, - MapMarker = 0xf041, - MapMarkerAlt = 0xf3c5, - MapPin = 0xf276, - MapSigns = 0xf277, - Markdown = 0xf60f, - Marker = 0xf5a1, - Mars = 0xf222, - MarsDouble = 0xf227, - MarsStroke = 0xf229, - MarsStrokeH = 0xf22b, - MarsStrokeV = 0xf22a, - Mask = 0xf6fa, - Mastodon = 0xf4f6, - Maxcdn = 0xf136, - Mdb = 0xf8ca, - Medal = 0xf5a2, - Medapps = 0xf3c6, - Medium = 0xf23a, - MediumM = 0xf3c7, - Medkit = 0xf0fa, - Medrt = 0xf3c8, - Meetup = 0xf2e0, - Megaport = 0xf5a3, - Meh = 0xf11a, - MehBlank = 0xf5a4, - MehRollingEyes = 0xf5a5, - Memory = 0xf538, - Mendeley = 0xf7b3, - Menorah = 0xf676, - Mercury = 0xf223, - Meteor = 0xf753, - Microblog = 0xf91a, - Microchip = 0xf2db, - Microphone = 0xf130, - MicrophoneAlt = 0xf3c9, - MicrophoneAltSlash = 0xf539, - MicrophoneSlash = 0xf131, - Microscope = 0xf610, - Microsoft = 0xf3ca, - Minus = 0xf068, - MinusCircle = 0xf056, - MinusSquare = 0xf146, - Mitten = 0xf7b5, - Mix = 0xf3cb, - Mixcloud = 0xf289, - Mixer = 0xf956, - Mizuni = 0xf3cc, - Mobile = 0xf10b, - MobileAlt = 0xf3cd, - Modx = 0xf285, - Monero = 0xf3d0, - MoneyBill = 0xf0d6, - MoneyBillAlt = 0xf3d1, - MoneyBillWave = 0xf53a, - MoneyBillWaveAlt = 0xf53b, - MoneyCheck = 0xf53c, - MoneyCheckAlt = 0xf53d, - Monument = 0xf5a6, - Moon = 0xf186, - MortarPestle = 0xf5a7, - Mosque = 0xf678, - Motorcycle = 0xf21c, - Mountain = 0xf6fc, - Mouse = 0xf8cc, - MousePointer = 0xf245, - MugHot = 0xf7b6, - Music = 0xf001, - Napster = 0xf3d2, - Neos = 0xf612, - NetworkWired = 0xf6ff, - Neuter = 0xf22c, - Newspaper = 0xf1ea, - Nimblr = 0xf5a8, - Node = 0xf419, - NodeJs = 0xf3d3, - NotEqual = 0xf53e, - NotesMedical = 0xf481, - Npm = 0xf3d4, - Ns8 = 0xf3d5, - Nutritionix = 0xf3d6, - ObjectGroup = 0xf247, - ObjectUngroup = 0xf248, - Odnoklassniki = 0xf263, - OdnoklassnikiSquare = 0xf264, - OilCan = 0xf613, - OldRepublic = 0xf510, - Om = 0xf679, - Opencart = 0xf23d, - Openid = 0xf19b, - Opera = 0xf26a, - OptinMonster = 0xf23c, - Orcid = 0xf8d2, - Osi = 0xf41a, - Otter = 0xf700, - Outdent = 0xf03b, - Page4 = 0xf3d7, - Pagelines = 0xf18c, - Pager = 0xf815, - PaintBrush = 0xf1fc, - PaintRoller = 0xf5aa, - Palette = 0xf53f, - Palfed = 0xf3d8, - Pallet = 0xf482, - Paperclip = 0xf0c6, - PaperPlane = 0xf1d8, - ParachuteBox = 0xf4cd, - Paragraph = 0xf1dd, - Parking = 0xf540, - Passport = 0xf5ab, - Pastafarianism = 0xf67b, - Paste = 0xf0ea, - Patreon = 0xf3d9, - Pause = 0xf04c, - PauseCircle = 0xf28b, - Paw = 0xf1b0, - Paypal = 0xf1ed, - Peace = 0xf67c, - Pen = 0xf304, - PenAlt = 0xf305, - PencilAlt = 0xf303, - PencilRuler = 0xf5ae, - PenFancy = 0xf5ac, - PenNib = 0xf5ad, - PennyArcade = 0xf704, - PenSquare = 0xf14b, - PeopleCarry = 0xf4ce, - PepperHot = 0xf816, - Percent = 0xf295, - Percentage = 0xf541, - Periscope = 0xf3da, - PersonBooth = 0xf756, - Phabricator = 0xf3db, - PhoenixFramework = 0xf3dc, - PhoenixSquadron = 0xf511, - Phone = 0xf095, - PhoneAlt = 0xf879, - PhoneSlash = 0xf3dd, - PhoneSquare = 0xf098, - PhoneSquareAlt = 0xf87b, - PhoneVolume = 0xf2a0, - PhotoVideo = 0xf87c, - Php = 0xf457, - PiedPiper = 0xf2ae, - PiedPiperAlt = 0xf1a8, - PiedPiperHat = 0xf4e5, - PiedPiperPp = 0xf1a7, - PiedPiperSquare = 0xf91e, - PiggyBank = 0xf4d3, - Pills = 0xf484, - Pinterest = 0xf0d2, - PinterestP = 0xf231, - PinterestSquare = 0xf0d3, - PizzaSlice = 0xf818, - PlaceOfWorship = 0xf67f, - Plane = 0xf072, - PlaneArrival = 0xf5af, - PlaneDeparture = 0xf5b0, - Play = 0xf04b, - PlayCircle = 0xf144, - Playstation = 0xf3df, - Plug = 0xf1e6, - Plus = 0xf067, - PlusCircle = 0xf055, - PlusSquare = 0xf0fe, - Podcast = 0xf2ce, - Poll = 0xf681, - PollH = 0xf682, - Poo = 0xf2fe, - Poop = 0xf619, - PooStorm = 0xf75a, - Portrait = 0xf3e0, - PoundSign = 0xf154, - PowerOff = 0xf011, - Pray = 0xf683, - PrayingHands = 0xf684, - Prescription = 0xf5b1, - PrescriptionBottle = 0xf485, - PrescriptionBottleAlt = 0xf486, - Print = 0xf02f, - Procedures = 0xf487, - ProductHunt = 0xf288, - ProjectDiagram = 0xf542, - Pushed = 0xf3e1, - PuzzlePiece = 0xf12e, - Python = 0xf3e2, - Qq = 0xf1d6, - Qrcode = 0xf029, - Question = 0xf128, - QuestionCircle = 0xf059, - Quidditch = 0xf458, - Quinscape = 0xf459, - Quora = 0xf2c4, - QuoteLeft = 0xf10d, - QuoteRight = 0xf10e, - Quran = 0xf687, - Radiation = 0xf7b9, - RadiationAlt = 0xf7ba, - Rainbow = 0xf75b, - Random = 0xf074, - RaspberryPi = 0xf7bb, - Ravelry = 0xf2d9, - React = 0xf41b, - Reacteurope = 0xf75d, - Readme = 0xf4d5, - Rebel = 0xf1d0, - Receipt = 0xf543, - RecordVinyl = 0xf8d9, - Recycle = 0xf1b8, - Reddit = 0xf1a1, - RedditAlien = 0xf281, - RedditSquare = 0xf1a2, - Redhat = 0xf7bc, - Redo = 0xf01e, - RedoAlt = 0xf2f9, - RedRiver = 0xf3e3, - Registered = 0xf25d, - RemoveFormat = 0xf87d, - Renren = 0xf18b, - Reply = 0xf3e5, - ReplyAll = 0xf122, - Replyd = 0xf3e6, - Republican = 0xf75e, - Researchgate = 0xf4f8, - Resolving = 0xf3e7, - Restroom = 0xf7bd, - Retweet = 0xf079, - Rev = 0xf5b2, - Ribbon = 0xf4d6, - Ring = 0xf70b, - Road = 0xf018, - Robot = 0xf544, - Rocket = 0xf135, - Rocketchat = 0xf3e8, - Rockrms = 0xf3e9, - Route = 0xf4d7, - RProject = 0xf4f7, - Rss = 0xf09e, - RssSquare = 0xf143, - RubleSign = 0xf158, - Ruler = 0xf545, - RulerCombined = 0xf546, - RulerHorizontal = 0xf547, - RulerVertical = 0xf548, - Running = 0xf70c, - RupeeSign = 0xf156, - SadCry = 0xf5b3, - SadTear = 0xf5b4, - Safari = 0xf267, - Salesforce = 0xf83b, - Sass = 0xf41e, - Satellite = 0xf7bf, - SatelliteDish = 0xf7c0, - Save = 0xf0c7, - Schlix = 0xf3ea, - School = 0xf549, - Screwdriver = 0xf54a, - Scribd = 0xf28a, - Scroll = 0xf70e, - SdCard = 0xf7c2, - Search = 0xf002, - SearchDollar = 0xf688, - Searchengin = 0xf3eb, - SearchLocation = 0xf689, - SearchMinus = 0xf010, - SearchPlus = 0xf00e, - Seedling = 0xf4d8, - Sellcast = 0xf2da, - Sellsy = 0xf213, - Server = 0xf233, - Servicestack = 0xf3ec, - Shapes = 0xf61f, - Share = 0xf064, - ShareAlt = 0xf1e0, - ShareAltSquare = 0xf1e1, - ShareSquare = 0xf14d, - ShekelSign = 0xf20b, - ShieldAlt = 0xf3ed, - Ship = 0xf21a, - ShippingFast = 0xf48b, - Shirtsinbulk = 0xf214, - ShoePrints = 0xf54b, - Shopify = 0xf957, - ShoppingBag = 0xf290, - ShoppingBasket = 0xf291, - ShoppingCart = 0xf07a, - Shopware = 0xf5b5, - Shower = 0xf2cc, - ShuttleVan = 0xf5b6, - Sign = 0xf4d9, - Signal = 0xf012, - Signature = 0xf5b7, - SignInAlt = 0xf2f6, - SignLanguage = 0xf2a7, - SignOutAlt = 0xf2f5, - SimCard = 0xf7c4, - Simplybuilt = 0xf215, - Sistrix = 0xf3ee, - Sitemap = 0xf0e8, - Sith = 0xf512, - Skating = 0xf7c5, - Sketch = 0xf7c6, - Skiing = 0xf7c9, - SkiingNordic = 0xf7ca, - Skull = 0xf54c, - SkullCrossbones = 0xf714, - Skyatlas = 0xf216, - Skype = 0xf17e, - Slack = 0xf198, - SlackHash = 0xf3ef, - Slash = 0xf715, - Sleigh = 0xf7cc, - SlidersH = 0xf1de, - Slideshare = 0xf1e7, - Smile = 0xf118, - SmileBeam = 0xf5b8, - SmileWink = 0xf4da, - Smog = 0xf75f, - Smoking = 0xf48d, - SmokingBan = 0xf54d, - Sms = 0xf7cd, - Snapchat = 0xf2ab, - SnapchatGhost = 0xf2ac, - SnapchatSquare = 0xf2ad, - Snowboarding = 0xf7ce, - Snowflake = 0xf2dc, - Snowman = 0xf7d0, - Snowplow = 0xf7d2, - Socks = 0xf696, - SolarPanel = 0xf5ba, - Sort = 0xf0dc, - SortAlphaDown = 0xf15d, - SortAlphaDownAlt = 0xf881, - SortAlphaUp = 0xf15e, - SortAlphaUpAlt = 0xf882, - SortAmountDown = 0xf160, - SortAmountDownAlt = 0xf884, - SortAmountUp = 0xf161, - SortAmountUpAlt = 0xf885, - SortDown = 0xf0dd, - SortNumericDown = 0xf162, - SortNumericDownAlt = 0xf886, - SortNumericUp = 0xf163, - SortNumericUpAlt = 0xf887, - SortUp = 0xf0de, - Soundcloud = 0xf1be, - Sourcetree = 0xf7d3, - Spa = 0xf5bb, - SpaceShuttle = 0xf197, - Speakap = 0xf3f3, - SpeakerDeck = 0xf83c, - SpellCheck = 0xf891, - Spider = 0xf717, - Spinner = 0xf110, - Splotch = 0xf5bc, - Spotify = 0xf1bc, - SprayCan = 0xf5bd, - Square = 0xf0c8, - SquareFull = 0xf45c, - SquareRootAlt = 0xf698, - Squarespace = 0xf5be, - StackExchange = 0xf18d, - StackOverflow = 0xf16c, - Stackpath = 0xf842, - Stamp = 0xf5bf, - Star = 0xf005, - StarAndCrescent = 0xf699, - StarHalf = 0xf089, - StarHalfAlt = 0xf5c0, - StarOfDavid = 0xf69a, - StarOfLife = 0xf621, - Staylinked = 0xf3f5, - Steam = 0xf1b6, - SteamSquare = 0xf1b7, - SteamSymbol = 0xf3f6, - StepBackward = 0xf048, - StepForward = 0xf051, - Stethoscope = 0xf0f1, - StickerMule = 0xf3f7, - StickyNote = 0xf249, - Stop = 0xf04d, - StopCircle = 0xf28d, - Stopwatch = 0xf2f2, - Store = 0xf54e, - StoreAlt = 0xf54f, - Strava = 0xf428, - Stream = 0xf550, - StreetView = 0xf21d, - Strikethrough = 0xf0cc, - Stripe = 0xf429, - StripeS = 0xf42a, - Stroopwafel = 0xf551, - Studiovinari = 0xf3f8, - Stumbleupon = 0xf1a4, - StumbleuponCircle = 0xf1a3, - Subscript = 0xf12c, - Subway = 0xf239, - Suitcase = 0xf0f2, - SuitcaseRolling = 0xf5c1, - Sun = 0xf185, - Superpowers = 0xf2dd, - Superscript = 0xf12b, - Supple = 0xf3f9, - Surprise = 0xf5c2, - Suse = 0xf7d6, - Swatchbook = 0xf5c3, - Swift = 0xf8e1, - Swimmer = 0xf5c4, - SwimmingPool = 0xf5c5, - Symfony = 0xf83d, - Synagogue = 0xf69b, - Sync = 0xf021, - SyncAlt = 0xf2f1, - Syringe = 0xf48e, - Table = 0xf0ce, - Tablet = 0xf10a, - TabletAlt = 0xf3fa, - TableTennis = 0xf45d, - Tablets = 0xf490, - TachometerAlt = 0xf3fd, - Tag = 0xf02b, - Tags = 0xf02c, - Tape = 0xf4db, - Tasks = 0xf0ae, - Taxi = 0xf1ba, - Teamspeak = 0xf4f9, - Teeth = 0xf62e, - TeethOpen = 0xf62f, - Telegram = 0xf2c6, - TelegramPlane = 0xf3fe, - TemperatureHigh = 0xf769, - TemperatureLow = 0xf76b, - TencentWeibo = 0xf1d5, - Tenge = 0xf7d7, - Terminal = 0xf120, - TextHeight = 0xf034, - TextWidth = 0xf035, - Th = 0xf00a, - TheaterMasks = 0xf630, - Themeco = 0xf5c6, - Themeisle = 0xf2b2, - TheRedYeti = 0xf69d, - Thermometer = 0xf491, - ThermometerEmpty = 0xf2cb, - ThermometerFull = 0xf2c7, - ThermometerHalf = 0xf2c9, - ThermometerQuarter = 0xf2ca, - ThermometerThreeQuarters = 0xf2c8, - ThinkPeaks = 0xf731, - ThLarge = 0xf009, - ThList = 0xf00b, - ThumbsDown = 0xf165, - ThumbsUp = 0xf164, - Thumbtack = 0xf08d, - TicketAlt = 0xf3ff, - Times = 0xf00d, - TimesCircle = 0xf057, - Tint = 0xf043, - TintSlash = 0xf5c7, - Tired = 0xf5c8, - ToggleOff = 0xf204, - ToggleOn = 0xf205, - Toilet = 0xf7d8, - ToiletPaper = 0xf71e, - Toolbox = 0xf552, - Tools = 0xf7d9, - Tooth = 0xf5c9, - Torah = 0xf6a0, - ToriiGate = 0xf6a1, - Tractor = 0xf722, - TradeFederation = 0xf513, - Trademark = 0xf25c, - TrafficLight = 0xf637, - Trailer = 0xf941, - Train = 0xf238, - Tram = 0xf7da, - Transgender = 0xf224, - TransgenderAlt = 0xf225, - Trash = 0xf1f8, - TrashAlt = 0xf2ed, - TrashRestore = 0xf829, - TrashRestoreAlt = 0xf82a, - Tree = 0xf1bb, - Trello = 0xf181, - Tripadvisor = 0xf262, - Trophy = 0xf091, - Truck = 0xf0d1, - TruckLoading = 0xf4de, - TruckMonster = 0xf63b, - TruckMoving = 0xf4df, - TruckPickup = 0xf63c, - Tshirt = 0xf553, - Tty = 0xf1e4, - Tumblr = 0xf173, - TumblrSquare = 0xf174, - Tv = 0xf26c, - Twitch = 0xf1e8, - Twitter = 0xf099, - TwitterSquare = 0xf081, - Typo3 = 0xf42b, - Uber = 0xf402, - Ubuntu = 0xf7df, - Uikit = 0xf403, - Umbraco = 0xf8e8, - Umbrella = 0xf0e9, - UmbrellaBeach = 0xf5ca, - Underline = 0xf0cd, - Undo = 0xf0e2, - UndoAlt = 0xf2ea, - Uniregistry = 0xf404, - Unity = 0xf949, - UniversalAccess = 0xf29a, - University = 0xf19c, - Unlink = 0xf127, - Unlock = 0xf09c, - UnlockAlt = 0xf13e, - Untappd = 0xf405, - Upload = 0xf093, - Ups = 0xf7e0, - Usb = 0xf287, - User = 0xf007, - UserAlt = 0xf406, - UserAltSlash = 0xf4fa, - UserAstronaut = 0xf4fb, - UserCheck = 0xf4fc, - UserCircle = 0xf2bd, - UserClock = 0xf4fd, - UserCog = 0xf4fe, - UserEdit = 0xf4ff, - UserFriends = 0xf500, - UserGraduate = 0xf501, - UserInjured = 0xf728, - UserLock = 0xf502, - UserMd = 0xf0f0, - UserMinus = 0xf503, - UserNinja = 0xf504, - UserNurse = 0xf82f, - UserPlus = 0xf234, - Users = 0xf0c0, - UsersCog = 0xf509, - UserSecret = 0xf21b, - UserShield = 0xf505, - UserSlash = 0xf506, - UserTag = 0xf507, - UserTie = 0xf508, - UserTimes = 0xf235, - Usps = 0xf7e1, - Ussunnah = 0xf407, - Utensils = 0xf2e7, - UtensilSpoon = 0xf2e5, - Vaadin = 0xf408, - VectorSquare = 0xf5cb, - Venus = 0xf221, - VenusDouble = 0xf226, - VenusMars = 0xf228, - Viacoin = 0xf237, - Viadeo = 0xf2a9, - ViadeoSquare = 0xf2aa, - Vial = 0xf492, - Vials = 0xf493, - Viber = 0xf409, - Video = 0xf03d, - VideoSlash = 0xf4e2, - Vihara = 0xf6a7, - Vimeo = 0xf40a, - VimeoSquare = 0xf194, - VimeoV = 0xf27d, - Vine = 0xf1ca, - Vk = 0xf189, - Vnv = 0xf40b, - Voicemail = 0xf897, - VolleyballBall = 0xf45f, - VolumeDown = 0xf027, - VolumeMute = 0xf6a9, - VolumeOff = 0xf026, - VolumeUp = 0xf028, - VoteYea = 0xf772, - VrCardboard = 0xf729, - Vuejs = 0xf41f, - Walking = 0xf554, - Wallet = 0xf555, - Warehouse = 0xf494, - Water = 0xf773, - WaveSquare = 0xf83e, - Waze = 0xf83f, - Weebly = 0xf5cc, - Weibo = 0xf18a, - Weight = 0xf496, - WeightHanging = 0xf5cd, - Weixin = 0xf1d7, - Whatsapp = 0xf232, - WhatsappSquare = 0xf40c, - Wheelchair = 0xf193, - Whmcs = 0xf40d, - Wifi = 0xf1eb, - WikipediaW = 0xf266, - Wind = 0xf72e, - WindowClose = 0xf410, - WindowMaximize = 0xf2d0, - WindowMinimize = 0xf2d1, - WindowRestore = 0xf2d2, - Windows = 0xf17a, - WineBottle = 0xf72f, - WineGlass = 0xf4e3, - WineGlassAlt = 0xf5ce, - Wix = 0xf5cf, - WizardsOfTheCoast = 0xf730, - WolfPackBattalion = 0xf514, - WonSign = 0xf159, - Wordpress = 0xf19a, - WordpressSimple = 0xf411, - Wpbeginner = 0xf297, - Wpexplorer = 0xf2de, - Wpforms = 0xf298, - Wpressr = 0xf3e4, - Wrench = 0xf0ad, - Xbox = 0xf412, - Xing = 0xf168, - XingSquare = 0xf169, - XRay = 0xf497, - Yahoo = 0xf19e, - Yammer = 0xf840, - Yandex = 0xf413, - YandexInternational = 0xf414, - Yarn = 0xf7e3, - YCombinator = 0xf23b, - Yelp = 0xf1e9, - YenSign = 0xf157, - YinYang = 0xf6ad, - Yoast = 0xf2b1, - Youtube = 0xf167, - YoutubeSquare = 0xf431, - Zhihu = 0xf63f, + + /// + /// The Font Awesome "500px" icon unicode character. + /// + _500Px = 0xF26E, + + /// + /// The Font Awesome "accessible-icon" icon unicode character. + /// + AccessibleIcon = 0xF368, + + /// + /// The Font Awesome "accusoft" icon unicode character. + /// + Accusoft = 0xF369, + + /// + /// The Font Awesome "acquisitions-incorporated" icon unicode character. + /// + AcquisitionsIncorporated = 0xF6AF, + + /// + /// The Font Awesome "ad" icon unicode character. + /// + Ad = 0xF641, + + /// + /// The Font Awesome "address-book" icon unicode character. + /// + AddressBook = 0xF2B9, + + /// + /// The Font Awesome "address-card" icon unicode character. + /// + AddressCard = 0xF2BB, + + /// + /// The Font Awesome "adjust" icon unicode character. + /// + Adjust = 0xF042, + + /// + /// The Font Awesome "adn" icon unicode character. + /// + Adn = 0xF17, + + /// + /// The Font Awesome "adobe" icon unicode character. + /// + Adobe = 0xF778, + + /// + /// The Font Awesome "adversal" icon unicode character. + /// + Adversal = 0xF36A, + + /// + /// The Font Awesome "affiliatetheme" icon unicode character. + /// + Affiliatetheme = 0xF36B, + + /// + /// The Font Awesome "airbnb" icon unicode character. + /// + Airbnb = 0xF834, + + /// + /// The Font Awesome "air-freshener" icon unicode character. + /// + AirFreshener = 0xF5D, + + /// + /// The Font Awesome "algolia" icon unicode character. + /// + Algolia = 0xF36C, + + /// + /// The Font Awesome "align-center" icon unicode character. + /// + AlignCenter = 0xF037, + + /// + /// The Font Awesome "align-justify" icon unicode character. + /// + AlignJustify = 0xF039, + + /// + /// The Font Awesome "align-left" icon unicode character. + /// + AlignLeft = 0xF036, + + /// + /// The Font Awesome "align-right" icon unicode character. + /// + AlignRight = 0xF038, + + /// + /// The Font Awesome "alipay" icon unicode character. + /// + Alipay = 0xF642, + + /// + /// The Font Awesome "allergies" icon unicode character. + /// + Allergies = 0xF461, + + /// + /// The Font Awesome "amazon" icon unicode character. + /// + Amazon = 0xF27, + + /// + /// The Font Awesome "amazon-pay" icon unicode character. + /// + AmazonPay = 0xF42C, + + /// + /// The Font Awesome "ambulance" icon unicode character. + /// + Ambulance = 0xF0F9, + + /// + /// The Font Awesome "american-sign-language-interpreting" icon unicode character. + /// + AmericanSignLanguageInterpreting = 0xF2A3, + + /// + /// The Font Awesome "amilia" icon unicode character. + /// + Amilia = 0xF36D, + + /// + /// The Font Awesome "anchor" icon unicode character. + /// + Anchor = 0xF13D, + + /// + /// The Font Awesome "android" icon unicode character. + /// + Android = 0xF17B, + + /// + /// The Font Awesome "angellist" icon unicode character. + /// + Angellist = 0xF209, + + /// + /// The Font Awesome "angle-double-down" icon unicode character. + /// + AngleDoubleDown = 0xF103, + + /// + /// The Font Awesome "angle-double-left" icon unicode character. + /// + AngleDoubleLeft = 0xF1, + + /// + /// The Font Awesome "angle-double-right" icon unicode character. + /// + AngleDoubleRight = 0xF101, + + /// + /// The Font Awesome "angle-double-up" icon unicode character. + /// + AngleDoubleUp = 0xF102, + + /// + /// The Font Awesome "angle-down" icon unicode character. + /// + AngleDown = 0xF107, + + /// + /// The Font Awesome "angle-left" icon unicode character. + /// + AngleLeft = 0xF104, + + /// + /// The Font Awesome "angle-right" icon unicode character. + /// + AngleRight = 0xF105, + + /// + /// The Font Awesome "angle-up" icon unicode character. + /// + AngleUp = 0xF106, + + /// + /// The Font Awesome "angry" icon unicode character. + /// + Angry = 0xF556, + + /// + /// The Font Awesome "angrycreative" icon unicode character. + /// + Angrycreative = 0xF36E, + + /// + /// The Font Awesome "angular" icon unicode character. + /// + Angular = 0xF42, + + /// + /// The Font Awesome "ankh" icon unicode character. + /// + Ankh = 0xF644, + + /// + /// The Font Awesome "apper" icon unicode character. + /// + Apper = 0xF371, + + /// + /// The Font Awesome "apple" icon unicode character. + /// + Apple = 0xF179, + + /// + /// The Font Awesome "apple-alt" icon unicode character. + /// + AppleAlt = 0xF5D1, + + /// + /// The Font Awesome "apple-pay" icon unicode character. + /// + ApplePay = 0xF415, + + /// + /// The Font Awesome "app-store" icon unicode character. + /// + AppStore = 0xF36F, + + /// + /// The Font Awesome "app-store-ios" icon unicode character. + /// + AppStoreIos = 0xF37, + + /// + /// The Font Awesome "archive" icon unicode character. + /// + Archive = 0xF187, + + /// + /// The Font Awesome "archway" icon unicode character. + /// + Archway = 0xF557, + + /// + /// The Font Awesome "arrow-alt-circle-down" icon unicode character. + /// + ArrowAltCircleDown = 0xF358, + + /// + /// The Font Awesome "arrow-alt-circle-left" icon unicode character. + /// + ArrowAltCircleLeft = 0xF359, + + /// + /// The Font Awesome "arrow-alt-circle-right" icon unicode character. + /// + ArrowAltCircleRight = 0xF35A, + + /// + /// The Font Awesome "arrow-alt-circle-up" icon unicode character. + /// + ArrowAltCircleUp = 0xF35B, + + /// + /// The Font Awesome "arrow-circle-down" icon unicode character. + /// + ArrowCircleDown = 0xF0AB, + + /// + /// The Font Awesome "arrow-circle-left" icon unicode character. + /// + ArrowCircleLeft = 0xF0A8, + + /// + /// The Font Awesome "arrow-circle-right" icon unicode character. + /// + ArrowCircleRight = 0xF0A9, + + /// + /// The Font Awesome "arrow-circle-up" icon unicode character. + /// + ArrowCircleUp = 0xF0AA, + + /// + /// The Font Awesome "arrow-down" icon unicode character. + /// + ArrowDown = 0xF063, + + /// + /// The Font Awesome "arrow-left" icon unicode character. + /// + ArrowLeft = 0xF06, + + /// + /// The Font Awesome "arrow-right" icon unicode character. + /// + ArrowRight = 0xF061, + + /// + /// The Font Awesome "arrows-alt" icon unicode character. + /// + ArrowsAlt = 0xF0B2, + + /// + /// The Font Awesome "arrows-alt-h" icon unicode character. + /// + ArrowsAltH = 0xF337, + + /// + /// The Font Awesome "arrows-alt-v" icon unicode character. + /// + ArrowsAltV = 0xF338, + + /// + /// The Font Awesome "arrow-up" icon unicode character. + /// + ArrowUp = 0xF062, + + /// + /// The Font Awesome "artstation" icon unicode character. + /// + Artstation = 0xF77A, + + /// + /// The Font Awesome "assistive-listening-systems" icon unicode character. + /// + AssistiveListeningSystems = 0xF2A2, + + /// + /// The Font Awesome "asterisk" icon unicode character. + /// + Asterisk = 0xF069, + + /// + /// The Font Awesome "asymmetrik" icon unicode character. + /// + Asymmetrik = 0xF372, + + /// + /// The Font Awesome "at" icon unicode character. + /// + At = 0xF1FA, + + /// + /// The Font Awesome "atlas" icon unicode character. + /// + Atlas = 0xF558, + + /// + /// The Font Awesome "atlassian" icon unicode character. + /// + Atlassian = 0xF77B, + + /// + /// The Font Awesome "atom" icon unicode character. + /// + Atom = 0xF5D2, + + /// + /// The Font Awesome "audible" icon unicode character. + /// + Audible = 0xF373, + + /// + /// The Font Awesome "audio-description" icon unicode character. + /// + AudioDescription = 0xF29E, + + /// + /// The Font Awesome "autoprefixer" icon unicode character. + /// + Autoprefixer = 0xF41C, + + /// + /// The Font Awesome "avianex" icon unicode character. + /// + Avianex = 0xF374, + + /// + /// The Font Awesome "aviato" icon unicode character. + /// + Aviato = 0xF421, + + /// + /// The Font Awesome "award" icon unicode character. + /// + Award = 0xF559, + + /// + /// The Font Awesome "aws" icon unicode character. + /// + Aws = 0xF375, + + /// + /// The Font Awesome "baby" icon unicode character. + /// + Baby = 0xF77C, + + /// + /// The Font Awesome "baby-carriage" icon unicode character. + /// + BabyCarriage = 0xF77D, + + /// + /// The Font Awesome "backspace" icon unicode character. + /// + Backspace = 0xF55A, + + /// + /// The Font Awesome "backward" icon unicode character. + /// + Backward = 0xF04A, + + /// + /// The Font Awesome "bacon" icon unicode character. + /// + Bacon = 0xF7E5, + + /// + /// The Font Awesome "bahai" icon unicode character. + /// + Bahai = 0xF666, + + /// + /// The Font Awesome "balance-scale" icon unicode character. + /// + BalanceScale = 0xF24E, + + /// + /// The Font Awesome "balance-scale-left" icon unicode character. + /// + BalanceScaleLeft = 0xF515, + + /// + /// The Font Awesome "balance-scale-right" icon unicode character. + /// + BalanceScaleRight = 0xF516, + + /// + /// The Font Awesome "ban" icon unicode character. + /// + Ban = 0xF05E, + + /// + /// The Font Awesome "band-aid" icon unicode character. + /// + BandAid = 0xF462, + + /// + /// The Font Awesome "bandcamp" icon unicode character. + /// + Bandcamp = 0xF2D5, + + /// + /// The Font Awesome "barcode" icon unicode character. + /// + Barcode = 0xF02A, + + /// + /// The Font Awesome "bars" icon unicode character. + /// + Bars = 0xF0C9, + + /// + /// The Font Awesome "baseball-ball" icon unicode character. + /// + BaseballBall = 0xF433, + + /// + /// The Font Awesome "basketball-ball" icon unicode character. + /// + BasketballBall = 0xF434, + + /// + /// The Font Awesome "bath" icon unicode character. + /// + Bath = 0xF2CD, + + /// + /// The Font Awesome "battery-empty" icon unicode character. + /// + BatteryEmpty = 0xF244, + + /// + /// The Font Awesome "battery-full" icon unicode character. + /// + BatteryFull = 0xF24, + + /// + /// The Font Awesome "battery-half" icon unicode character. + /// + BatteryHalf = 0xF242, + + /// + /// The Font Awesome "battery-quarter" icon unicode character. + /// + BatteryQuarter = 0xF243, + + /// + /// The Font Awesome "battery-three-quarters" icon unicode character. + /// + BatteryThreeQuarters = 0xF241, + + /// + /// The Font Awesome "battle-net" icon unicode character. + /// + BattleNet = 0xF835, + + /// + /// The Font Awesome "bed" icon unicode character. + /// + Bed = 0xF236, + + /// + /// The Font Awesome "beer" icon unicode character. + /// + Beer = 0xF0FC, + + /// + /// The Font Awesome "behance" icon unicode character. + /// + Behance = 0xF1B4, + + /// + /// The Font Awesome "behance-square" icon unicode character. + /// + BehanceSquare = 0xF1B5, + + /// + /// The Font Awesome "bell" icon unicode character. + /// + Bell = 0xF0F3, + + /// + /// The Font Awesome "bell-slash" icon unicode character. + /// + BellSlash = 0xF1F6, + + /// + /// The Font Awesome "bezier-curve" icon unicode character. + /// + BezierCurve = 0xF55B, + + /// + /// The Font Awesome "bible" icon unicode character. + /// + Bible = 0xF647, + + /// + /// The Font Awesome "bicycle" icon unicode character. + /// + Bicycle = 0xF206, + + /// + /// The Font Awesome "biking" icon unicode character. + /// + Biking = 0xF84A, + + /// + /// The Font Awesome "bimobject" icon unicode character. + /// + Bimobject = 0xF378, + + /// + /// The Font Awesome "binoculars" icon unicode character. + /// + Binoculars = 0xF1E5, + + /// + /// The Font Awesome "biohazard" icon unicode character. + /// + Biohazard = 0xF78, + + /// + /// The Font Awesome "birthday-cake" icon unicode character. + /// + BirthdayCake = 0xF1FD, + + /// + /// The Font Awesome "bitbucket" icon unicode character. + /// + Bitbucket = 0xF171, + + /// + /// The Font Awesome "bitcoin" icon unicode character. + /// + Bitcoin = 0xF379, + + /// + /// The Font Awesome "bity" icon unicode character. + /// + Bity = 0xF37A, + + /// + /// The Font Awesome "blackberry" icon unicode character. + /// + Blackberry = 0xF37B, + + /// + /// The Font Awesome "black-tie" icon unicode character. + /// + BlackTie = 0xF27E, + + /// + /// The Font Awesome "blender" icon unicode character. + /// + Blender = 0xF517, + + /// + /// The Font Awesome "blender-phone" icon unicode character. + /// + BlenderPhone = 0xF6B6, + + /// + /// The Font Awesome "blind" icon unicode character. + /// + Blind = 0xF29D, + + /// + /// The Font Awesome "blog" icon unicode character. + /// + Blog = 0xF781, + + /// + /// The Font Awesome "blogger" icon unicode character. + /// + Blogger = 0xF37C, + + /// + /// The Font Awesome "blogger-b" icon unicode character. + /// + BloggerB = 0xF37D, + + /// + /// The Font Awesome "bluetooth" icon unicode character. + /// + Bluetooth = 0xF293, + + /// + /// The Font Awesome "bluetooth-b" icon unicode character. + /// + BluetoothB = 0xF294, + + /// + /// The Font Awesome "bold" icon unicode character. + /// + Bold = 0xF032, + + /// + /// The Font Awesome "bolt" icon unicode character. + /// + Bolt = 0xF0E7, + + /// + /// The Font Awesome "bomb" icon unicode character. + /// + Bomb = 0xF1E2, + + /// + /// The Font Awesome "bone" icon unicode character. + /// + Bone = 0xF5D7, + + /// + /// The Font Awesome "bong" icon unicode character. + /// + Bong = 0xF55C, + + /// + /// The Font Awesome "book" icon unicode character. + /// + Book = 0xF02D, + + /// + /// The Font Awesome "book-dead" icon unicode character. + /// + BookDead = 0xF6B7, + + /// + /// The Font Awesome "bookmark" icon unicode character. + /// + Bookmark = 0xF02E, + + /// + /// The Font Awesome "book-medical" icon unicode character. + /// + BookMedical = 0xF7E6, + + /// + /// The Font Awesome "book-open" icon unicode character. + /// + BookOpen = 0xF518, + + /// + /// The Font Awesome "book-reader" icon unicode character. + /// + BookReader = 0xF5DA, + + /// + /// The Font Awesome "bootstrap" icon unicode character. + /// + Bootstrap = 0xF836, + + /// + /// The Font Awesome "border-all" icon unicode character. + /// + BorderAll = 0xF84C, + + /// + /// The Font Awesome "border-none" icon unicode character. + /// + BorderNone = 0xF85, + + /// + /// The Font Awesome "border-style" icon unicode character. + /// + BorderStyle = 0xF853, + + /// + /// The Font Awesome "bowling-ball" icon unicode character. + /// + BowlingBall = 0xF436, + + /// + /// The Font Awesome "box" icon unicode character. + /// + Box = 0xF466, + + /// + /// The Font Awesome "boxes" icon unicode character. + /// + Boxes = 0xF468, + + /// + /// The Font Awesome "box-open" icon unicode character. + /// + BoxOpen = 0xF49E, + + /// + /// The Font Awesome "braille" icon unicode character. + /// + Braille = 0xF2A1, + + /// + /// The Font Awesome "brain" icon unicode character. + /// + Brain = 0xF5DC, + + /// + /// The Font Awesome "bread-slice" icon unicode character. + /// + BreadSlice = 0xF7EC, + + /// + /// The Font Awesome "briefcase" icon unicode character. + /// + Briefcase = 0xF0B1, + + /// + /// The Font Awesome "briefcase-medical" icon unicode character. + /// + BriefcaseMedical = 0xF469, + + /// + /// The Font Awesome "broadcast-tower" icon unicode character. + /// + BroadcastTower = 0xF519, + + /// + /// The Font Awesome "broom" icon unicode character. + /// + Broom = 0xF51A, + + /// + /// The Font Awesome "brush" icon unicode character. + /// + Brush = 0xF55D, + + /// + /// The Font Awesome "btc" icon unicode character. + /// + Btc = 0xF15A, + + /// + /// The Font Awesome "buffer" icon unicode character. + /// + Buffer = 0xF837, + + /// + /// The Font Awesome "bug" icon unicode character. + /// + Bug = 0xF188, + + /// + /// The Font Awesome "building" icon unicode character. + /// + Building = 0xF1AD, + + /// + /// The Font Awesome "bullhorn" icon unicode character. + /// + Bullhorn = 0xF0A1, + + /// + /// The Font Awesome "bullseye" icon unicode character. + /// + Bullseye = 0xF14, + + /// + /// The Font Awesome "burn" icon unicode character. + /// + Burn = 0xF46A, + + /// + /// The Font Awesome "buromobelexperte" icon unicode character. + /// + Buromobelexperte = 0xF37F, + + /// + /// The Font Awesome "bus" icon unicode character. + /// + Bus = 0xF207, + + /// + /// The Font Awesome "bus-alt" icon unicode character. + /// + BusAlt = 0xF55E, + + /// + /// The Font Awesome "business-time" icon unicode character. + /// + BusinessTime = 0xF64A, + + /// + /// The Font Awesome "buy-n-large" icon unicode character. + /// + BuyNLarge = 0xF8A6, + + /// + /// The Font Awesome "buysellads" icon unicode character. + /// + Buysellads = 0xF20D, + + /// + /// The Font Awesome "calculator" icon unicode character. + /// + Calculator = 0xF1EC, + + /// + /// The Font Awesome "calendar" icon unicode character. + /// + Calendar = 0xF133, + + /// + /// The Font Awesome "calendar-alt" icon unicode character. + /// + CalendarAlt = 0xF073, + + /// + /// The Font Awesome "calendar-check" icon unicode character. + /// + CalendarCheck = 0xF274, + + /// + /// The Font Awesome "calendar-day" icon unicode character. + /// + CalendarDay = 0xF783, + + /// + /// The Font Awesome "calendar-minus" icon unicode character. + /// + CalendarMinus = 0xF272, + + /// + /// The Font Awesome "calendar-plus" icon unicode character. + /// + CalendarPlus = 0xF271, + + /// + /// The Font Awesome "calendar-times" icon unicode character. + /// + CalendarTimes = 0xF273, + + /// + /// The Font Awesome "calendar-week" icon unicode character. + /// + CalendarWeek = 0xF784, + + /// + /// The Font Awesome "camera" icon unicode character. + /// + Camera = 0xF03, + + /// + /// The Font Awesome "camera-retro" icon unicode character. + /// + CameraRetro = 0xF083, + + /// + /// The Font Awesome "campground" icon unicode character. + /// + Campground = 0xF6BB, + + /// + /// The Font Awesome "canadian-maple-leaf" icon unicode character. + /// + CanadianMapleLeaf = 0xF785, + + /// + /// The Font Awesome "candy-cane" icon unicode character. + /// + CandyCane = 0xF786, + + /// + /// The Font Awesome "cannabis" icon unicode character. + /// + Cannabis = 0xF55F, + + /// + /// The Font Awesome "capsules" icon unicode character. + /// + Capsules = 0xF46B, + + /// + /// The Font Awesome "car" icon unicode character. + /// + Car = 0xF1B9, + + /// + /// The Font Awesome "car-alt" icon unicode character. + /// + CarAlt = 0xF5DE, + + /// + /// The Font Awesome "caravan" icon unicode character. + /// + Caravan = 0xF8FF, + + /// + /// The Font Awesome "car-battery" icon unicode character. + /// + CarBattery = 0xF5DF, + + /// + /// The Font Awesome "car-crash" icon unicode character. + /// + CarCrash = 0xF5E1, + + /// + /// The Font Awesome "caret-down" icon unicode character. + /// + CaretDown = 0xF0D7, + + /// + /// The Font Awesome "caret-left" icon unicode character. + /// + CaretLeft = 0xF0D9, + + /// + /// The Font Awesome "caret-right" icon unicode character. + /// + CaretRight = 0xF0DA, + + /// + /// The Font Awesome "caret-square-down" icon unicode character. + /// + CaretSquareDown = 0xF15, + + /// + /// The Font Awesome "caret-square-left" icon unicode character. + /// + CaretSquareLeft = 0xF191, + + /// + /// The Font Awesome "caret-square-right" icon unicode character. + /// + CaretSquareRight = 0xF152, + + /// + /// The Font Awesome "caret-square-up" icon unicode character. + /// + CaretSquareUp = 0xF151, + + /// + /// The Font Awesome "caret-up" icon unicode character. + /// + CaretUp = 0xF0D8, + + /// + /// The Font Awesome "carrot" icon unicode character. + /// + Carrot = 0xF787, + + /// + /// The Font Awesome "car-side" icon unicode character. + /// + CarSide = 0xF5E4, + + /// + /// The Font Awesome "cart-arrow-down" icon unicode character. + /// + CartArrowDown = 0xF218, + + /// + /// The Font Awesome "cart-plus" icon unicode character. + /// + CartPlus = 0xF217, + + /// + /// The Font Awesome "cash-register" icon unicode character. + /// + CashRegister = 0xF788, + + /// + /// The Font Awesome "cat" icon unicode character. + /// + Cat = 0xF6BE, + + /// + /// The Font Awesome "cc-amazon-pay" icon unicode character. + /// + CcAmazonPay = 0xF42D, + + /// + /// The Font Awesome "cc-amex" icon unicode character. + /// + CcAmex = 0xF1F3, + + /// + /// The Font Awesome "cc-apple-pay" icon unicode character. + /// + CcApplePay = 0xF416, + + /// + /// The Font Awesome "cc-diners-club" icon unicode character. + /// + CcDinersClub = 0xF24C, + + /// + /// The Font Awesome "cc-discover" icon unicode character. + /// + CcDiscover = 0xF1F2, + + /// + /// The Font Awesome "cc-jcb" icon unicode character. + /// + CcJcb = 0xF24B, + + /// + /// The Font Awesome "cc-mastercard" icon unicode character. + /// + CcMastercard = 0xF1F1, + + /// + /// The Font Awesome "cc-paypal" icon unicode character. + /// + CcPaypal = 0xF1F4, + + /// + /// The Font Awesome "cc-stripe" icon unicode character. + /// + CcStripe = 0xF1F5, + + /// + /// The Font Awesome "cc-visa" icon unicode character. + /// + CcVisa = 0xF1F, + + /// + /// The Font Awesome "centercode" icon unicode character. + /// + Centercode = 0xF38, + + /// + /// The Font Awesome "centos" icon unicode character. + /// + Centos = 0xF789, + + /// + /// The Font Awesome "certificate" icon unicode character. + /// + Certificate = 0xF0A3, + + /// + /// The Font Awesome "chair" icon unicode character. + /// + Chair = 0xF6C, + + /// + /// The Font Awesome "chalkboard" icon unicode character. + /// + Chalkboard = 0xF51B, + + /// + /// The Font Awesome "chalkboard-teacher" icon unicode character. + /// + ChalkboardTeacher = 0xF51C, + + /// + /// The Font Awesome "charging-station" icon unicode character. + /// + ChargingStation = 0xF5E7, + + /// + /// The Font Awesome "chart-area" icon unicode character. + /// + ChartArea = 0xF1FE, + + /// + /// The Font Awesome "chart-bar" icon unicode character. + /// + ChartBar = 0xF08, + + /// + /// The Font Awesome "chart-line" icon unicode character. + /// + ChartLine = 0xF201, + + /// + /// The Font Awesome "chart-pie" icon unicode character. + /// + ChartPie = 0xF2, + + /// + /// The Font Awesome "check" icon unicode character. + /// + Check = 0xF00C, + + /// + /// The Font Awesome "check-circle" icon unicode character. + /// + CheckCircle = 0xF058, + + /// + /// The Font Awesome "check-double" icon unicode character. + /// + CheckDouble = 0xF56, + + /// + /// The Font Awesome "check-square" icon unicode character. + /// + CheckSquare = 0xF14A, + + /// + /// The Font Awesome "cheese" icon unicode character. + /// + Cheese = 0xF7EF, + + /// + /// The Font Awesome "chess" icon unicode character. + /// + Chess = 0xF439, + + /// + /// The Font Awesome "chess-bishop" icon unicode character. + /// + ChessBishop = 0xF43A, + + /// + /// The Font Awesome "chess-board" icon unicode character. + /// + ChessBoard = 0xF43C, + + /// + /// The Font Awesome "chess-king" icon unicode character. + /// + ChessKing = 0xF43F, + + /// + /// The Font Awesome "chess-knight" icon unicode character. + /// + ChessKnight = 0xF441, + + /// + /// The Font Awesome "chess-pawn" icon unicode character. + /// + ChessPawn = 0xF443, + + /// + /// The Font Awesome "chess-queen" icon unicode character. + /// + ChessQueen = 0xF445, + + /// + /// The Font Awesome "chess-rook" icon unicode character. + /// + ChessRook = 0xF447, + + /// + /// The Font Awesome "chevron-circle-down" icon unicode character. + /// + ChevronCircleDown = 0xF13A, + + /// + /// The Font Awesome "chevron-circle-left" icon unicode character. + /// + ChevronCircleLeft = 0xF137, + + /// + /// The Font Awesome "chevron-circle-right" icon unicode character. + /// + ChevronCircleRight = 0xF138, + + /// + /// The Font Awesome "chevron-circle-up" icon unicode character. + /// + ChevronCircleUp = 0xF139, + + /// + /// The Font Awesome "chevron-down" icon unicode character. + /// + ChevronDown = 0xF078, + + /// + /// The Font Awesome "chevron-left" icon unicode character. + /// + ChevronLeft = 0xF053, + + /// + /// The Font Awesome "chevron-right" icon unicode character. + /// + ChevronRight = 0xF054, + + /// + /// The Font Awesome "chevron-up" icon unicode character. + /// + ChevronUp = 0xF077, + + /// + /// The Font Awesome "child" icon unicode character. + /// + Child = 0xF1AE, + + /// + /// The Font Awesome "chrome" icon unicode character. + /// + Chrome = 0xF268, + + /// + /// The Font Awesome "chromecast" icon unicode character. + /// + Chromecast = 0xF838, + + /// + /// The Font Awesome "church" icon unicode character. + /// + Church = 0xF51D, + + /// + /// The Font Awesome "circle" icon unicode character. + /// + Circle = 0xF111, + + /// + /// The Font Awesome "circle-notch" icon unicode character. + /// + CircleNotch = 0xF1CE, + + /// + /// The Font Awesome "city" icon unicode character. + /// + City = 0xF64F, + + /// + /// The Font Awesome "clinic-medical" icon unicode character. + /// + ClinicMedical = 0xF7F2, + + /// + /// The Font Awesome "clipboard" icon unicode character. + /// + Clipboard = 0xF328, + + /// + /// The Font Awesome "clipboard-check" icon unicode character. + /// + ClipboardCheck = 0xF46C, + + /// + /// The Font Awesome "clipboard-list" icon unicode character. + /// + ClipboardList = 0xF46D, + + /// + /// The Font Awesome "clock" icon unicode character. + /// + Clock = 0xF017, + + /// + /// The Font Awesome "clone" icon unicode character. + /// + Clone = 0xF24D, + + /// + /// The Font Awesome "closed-captioning" icon unicode character. + /// + ClosedCaptioning = 0xF20A, + + /// + /// The Font Awesome "cloud" icon unicode character. + /// + Cloud = 0xF0C2, + + /// + /// The Font Awesome "cloud-download-alt" icon unicode character. + /// + CloudDownloadAlt = 0xF381, + + /// + /// The Font Awesome "cloud-meatball" icon unicode character. + /// + CloudMeatball = 0xF73B, + + /// + /// The Font Awesome "cloud-moon" icon unicode character. + /// + CloudMoon = 0xF6C3, + + /// + /// The Font Awesome "cloud-moon-rain" icon unicode character. + /// + CloudMoonRain = 0xF73C, + + /// + /// The Font Awesome "cloud-rain" icon unicode character. + /// + CloudRain = 0xF73D, + + /// + /// The Font Awesome "cloudscale" icon unicode character. + /// + Cloudscale = 0xF383, + + /// + /// The Font Awesome "cloud-showers-heavy" icon unicode character. + /// + CloudShowersHeavy = 0xF74, + + /// + /// The Font Awesome "cloudsmith" icon unicode character. + /// + Cloudsmith = 0xF384, + + /// + /// The Font Awesome "cloud-sun" icon unicode character. + /// + CloudSun = 0xF6C4, + + /// + /// The Font Awesome "cloud-sun-rain" icon unicode character. + /// + CloudSunRain = 0xF743, + + /// + /// The Font Awesome "cloud-upload-alt" icon unicode character. + /// + CloudUploadAlt = 0xF382, + + /// + /// The Font Awesome "cloudversify" icon unicode character. + /// + Cloudversify = 0xF385, + + /// + /// The Font Awesome "cocktail" icon unicode character. + /// + Cocktail = 0xF561, + + /// + /// The Font Awesome "code" icon unicode character. + /// + Code = 0xF121, + + /// + /// The Font Awesome "code-branch" icon unicode character. + /// + CodeBranch = 0xF126, + + /// + /// The Font Awesome "codepen" icon unicode character. + /// + Codepen = 0xF1CB, + + /// + /// The Font Awesome "codiepie" icon unicode character. + /// + Codiepie = 0xF284, + + /// + /// The Font Awesome "coffee" icon unicode character. + /// + Coffee = 0xF0F4, + + /// + /// The Font Awesome "cog" icon unicode character. + /// + Cog = 0xF013, + + /// + /// The Font Awesome "cogs" icon unicode character. + /// + Cogs = 0xF085, + + /// + /// The Font Awesome "coins" icon unicode character. + /// + Coins = 0xF51E, + + /// + /// The Font Awesome "columns" icon unicode character. + /// + Columns = 0xF0DB, + + /// + /// The Font Awesome "comment" icon unicode character. + /// + Comment = 0xF075, + + /// + /// The Font Awesome "comment-alt" icon unicode character. + /// + CommentAlt = 0xF27A, + + /// + /// The Font Awesome "comment-dollar" icon unicode character. + /// + CommentDollar = 0xF651, + + /// + /// The Font Awesome "comment-dots" icon unicode character. + /// + CommentDots = 0xF4AD, + + /// + /// The Font Awesome "comment-medical" icon unicode character. + /// + CommentMedical = 0xF7F5, + + /// + /// The Font Awesome "comments" icon unicode character. + /// + Comments = 0xF086, + + /// + /// The Font Awesome "comments-dollar" icon unicode character. + /// + CommentsDollar = 0xF653, + + /// + /// The Font Awesome "comment-slash" icon unicode character. + /// + CommentSlash = 0xF4B3, + + /// + /// The Font Awesome "compact-disc" icon unicode character. + /// + CompactDisc = 0xF51F, + + /// + /// The Font Awesome "compass" icon unicode character. + /// + Compass = 0xF14E, + + /// + /// The Font Awesome "compress" icon unicode character. + /// + Compress = 0xF066, + + /// + /// The Font Awesome "compress-alt" icon unicode character. + /// + CompressAlt = 0xF422, + + /// + /// The Font Awesome "compress-arrows-alt" icon unicode character. + /// + CompressArrowsAlt = 0xF78C, + + /// + /// The Font Awesome "concierge-bell" icon unicode character. + /// + ConciergeBell = 0xF562, + + /// + /// The Font Awesome "confluence" icon unicode character. + /// + Confluence = 0xF78D, + + /// + /// The Font Awesome "connectdevelop" icon unicode character. + /// + Connectdevelop = 0xF20E, + + /// + /// The Font Awesome "contao" icon unicode character. + /// + Contao = 0xF26D, + + /// + /// The Font Awesome "cookie" icon unicode character. + /// + Cookie = 0xF563, + + /// + /// The Font Awesome "cookie-bite" icon unicode character. + /// + CookieBite = 0xF564, + + /// + /// The Font Awesome "copy" icon unicode character. + /// + Copy = 0xF0C5, + + /// + /// The Font Awesome "copyright" icon unicode character. + /// + Copyright = 0xF1F9, + + /// + /// The Font Awesome "cotton-bureau" icon unicode character. + /// + CottonBureau = 0xF89E, + + /// + /// The Font Awesome "couch" icon unicode character. + /// + Couch = 0xF4B8, + + /// + /// The Font Awesome "cpanel" icon unicode character. + /// + Cpanel = 0xF388, + + /// + /// The Font Awesome "creative-commons" icon unicode character. + /// + CreativeCommons = 0xF25E, + + /// + /// The Font Awesome "creative-commons-by" icon unicode character. + /// + CreativeCommonsBy = 0xF4E7, + + /// + /// The Font Awesome "creative-commons-nc" icon unicode character. + /// + CreativeCommonsNc = 0xF4E8, + + /// + /// The Font Awesome "creative-commons-nc-eu" icon unicode character. + /// + CreativeCommonsNcEu = 0xF4E9, + + /// + /// The Font Awesome "creative-commons-nc-jp" icon unicode character. + /// + CreativeCommonsNcJp = 0xF4EA, + + /// + /// The Font Awesome "creative-commons-nd" icon unicode character. + /// + CreativeCommonsNd = 0xF4EB, + + /// + /// The Font Awesome "creative-commons-pd" icon unicode character. + /// + CreativeCommonsPd = 0xF4EC, + + /// + /// The Font Awesome "creative-commons-pd-alt" icon unicode character. + /// + CreativeCommonsPdAlt = 0xF4ED, + + /// + /// The Font Awesome "creative-commons-remix" icon unicode character. + /// + CreativeCommonsRemix = 0xF4EE, + + /// + /// The Font Awesome "creative-commons-sa" icon unicode character. + /// + CreativeCommonsSa = 0xF4EF, + + /// + /// The Font Awesome "creative-commons-sampling" icon unicode character. + /// + CreativeCommonsSampling = 0xF4F, + + /// + /// The Font Awesome "creative-commons-sampling-plus" icon unicode character. + /// + CreativeCommonsSamplingPlus = 0xF4F1, + + /// + /// The Font Awesome "creative-commons-share" icon unicode character. + /// + CreativeCommonsShare = 0xF4F2, + + /// + /// The Font Awesome "creative-commons-zero" icon unicode character. + /// + CreativeCommonsZero = 0xF4F3, + + /// + /// The Font Awesome "credit-card" icon unicode character. + /// + CreditCard = 0xF09D, + + /// + /// The Font Awesome "critical-role" icon unicode character. + /// + CriticalRole = 0xF6C9, + + /// + /// The Font Awesome "crop" icon unicode character. + /// + Crop = 0xF125, + + /// + /// The Font Awesome "crop-alt" icon unicode character. + /// + CropAlt = 0xF565, + + /// + /// The Font Awesome "cross" icon unicode character. + /// + Cross = 0xF654, + + /// + /// The Font Awesome "crosshairs" icon unicode character. + /// + Crosshairs = 0xF05B, + + /// + /// The Font Awesome "crow" icon unicode character. + /// + Crow = 0xF52, + + /// + /// The Font Awesome "crown" icon unicode character. + /// + Crown = 0xF521, + + /// + /// The Font Awesome "crutch" icon unicode character. + /// + Crutch = 0xF7F7, + + /// + /// The Font Awesome "css3" icon unicode character. + /// + Css3 = 0xF13C, + + /// + /// The Font Awesome "css3-alt" icon unicode character. + /// + Css3Alt = 0xF38B, + + /// + /// The Font Awesome "cube" icon unicode character. + /// + Cube = 0xF1B2, + + /// + /// The Font Awesome "cubes" icon unicode character. + /// + Cubes = 0xF1B3, + + /// + /// The Font Awesome "cut" icon unicode character. + /// + Cut = 0xF0C4, + + /// + /// The Font Awesome "cuttlefish" icon unicode character. + /// + Cuttlefish = 0xF38C, + + /// + /// The Font Awesome "dailymotion" icon unicode character. + /// + Dailymotion = 0xF952, + + /// + /// The Font Awesome "d-and-d" icon unicode character. + /// + DAndD = 0xF38D, + + /// + /// The Font Awesome "d-and-d-beyond" icon unicode character. + /// + DAndDBeyond = 0xF6CA, + + /// + /// The Font Awesome "dashcube" icon unicode character. + /// + Dashcube = 0xF21, + + /// + /// The Font Awesome "database" icon unicode character. + /// + Database = 0xF1C, + + /// + /// The Font Awesome "deaf" icon unicode character. + /// + Deaf = 0xF2A4, + + /// + /// The Font Awesome "delicious" icon unicode character. + /// + Delicious = 0xF1A5, + + /// + /// The Font Awesome "democrat" icon unicode character. + /// + Democrat = 0xF747, + + /// + /// The Font Awesome "deploydog" icon unicode character. + /// + Deploydog = 0xF38E, + + /// + /// The Font Awesome "deskpro" icon unicode character. + /// + Deskpro = 0xF38F, + + /// + /// The Font Awesome "desktop" icon unicode character. + /// + Desktop = 0xF108, + + /// + /// The Font Awesome "dev" icon unicode character. + /// + Dev = 0xF6CC, + + /// + /// The Font Awesome "deviantart" icon unicode character. + /// + Deviantart = 0xF1BD, + + /// + /// The Font Awesome "dharmachakra" icon unicode character. + /// + Dharmachakra = 0xF655, + + /// + /// The Font Awesome "dhl" icon unicode character. + /// + Dhl = 0xF79, + + /// + /// The Font Awesome "diagnoses" icon unicode character. + /// + Diagnoses = 0xF47, + + /// + /// The Font Awesome "diaspora" icon unicode character. + /// + Diaspora = 0xF791, + + /// + /// The Font Awesome "dice" icon unicode character. + /// + Dice = 0xF522, + + /// + /// The Font Awesome "dice-d20" icon unicode character. + /// + DiceD20 = 0xF6CF, + + /// + /// The Font Awesome "dice-d6" icon unicode character. + /// + DiceD6 = 0xF6D1, + + /// + /// The Font Awesome "dice-five" icon unicode character. + /// + DiceFive = 0xF523, + + /// + /// The Font Awesome "dice-four" icon unicode character. + /// + DiceFour = 0xF524, + + /// + /// The Font Awesome "dice-one" icon unicode character. + /// + DiceOne = 0xF525, + + /// + /// The Font Awesome "dice-six" icon unicode character. + /// + DiceSix = 0xF526, + + /// + /// The Font Awesome "dice-three" icon unicode character. + /// + DiceThree = 0xF527, + + /// + /// The Font Awesome "dice-two" icon unicode character. + /// + DiceTwo = 0xF528, + + /// + /// The Font Awesome "digg" icon unicode character. + /// + Digg = 0xF1A6, + + /// + /// The Font Awesome "digital-ocean" icon unicode character. + /// + DigitalOcean = 0xF391, + + /// + /// The Font Awesome "digital-tachograph" icon unicode character. + /// + DigitalTachograph = 0xF566, + + /// + /// The Font Awesome "directions" icon unicode character. + /// + Directions = 0xF5EB, + + /// + /// The Font Awesome "discord" icon unicode character. + /// + Discord = 0xF392, + + /// + /// The Font Awesome "discourse" icon unicode character. + /// + Discourse = 0xF393, + + /// + /// The Font Awesome "divide" icon unicode character. + /// + Divide = 0xF529, + + /// + /// The Font Awesome "dizzy" icon unicode character. + /// + Dizzy = 0xF567, + + /// + /// The Font Awesome "dna" icon unicode character. + /// + Dna = 0xF471, + + /// + /// The Font Awesome "dochub" icon unicode character. + /// + Dochub = 0xF394, + + /// + /// The Font Awesome "docker" icon unicode character. + /// + Docker = 0xF395, + + /// + /// The Font Awesome "dog" icon unicode character. + /// + Dog = 0xF6D3, + + /// + /// The Font Awesome "dollar-sign" icon unicode character. + /// + DollarSign = 0xF155, + + /// + /// The Font Awesome "dolly" icon unicode character. + /// + Dolly = 0xF472, + + /// + /// The Font Awesome "dolly-flatbed" icon unicode character. + /// + DollyFlatbed = 0xF474, + + /// + /// The Font Awesome "donate" icon unicode character. + /// + Donate = 0xF4B9, + + /// + /// The Font Awesome "door-closed" icon unicode character. + /// + DoorClosed = 0xF52A, + + /// + /// The Font Awesome "door-open" icon unicode character. + /// + DoorOpen = 0xF52B, + + /// + /// The Font Awesome "dot-circle" icon unicode character. + /// + DotCircle = 0xF192, + + /// + /// The Font Awesome "dove" icon unicode character. + /// + Dove = 0xF4BA, + + /// + /// The Font Awesome "download" icon unicode character. + /// + Download = 0xF019, + + /// + /// The Font Awesome "draft2digital" icon unicode character. + /// + Draft2digital = 0xF396, + + /// + /// The Font Awesome "drafting-compass" icon unicode character. + /// + DraftingCompass = 0xF568, + + /// + /// The Font Awesome "dragon" icon unicode character. + /// + Dragon = 0xF6D5, + + /// + /// The Font Awesome "draw-polygon" icon unicode character. + /// + DrawPolygon = 0xF5EE, + + /// + /// The Font Awesome "dribbble" icon unicode character. + /// + Dribbble = 0xF17D, + + /// + /// The Font Awesome "dribbble-square" icon unicode character. + /// + DribbbleSquare = 0xF397, + + /// + /// The Font Awesome "dropbox" icon unicode character. + /// + Dropbox = 0xF16B, + + /// + /// The Font Awesome "drum" icon unicode character. + /// + Drum = 0xF569, + + /// + /// The Font Awesome "drum-steelpan" icon unicode character. + /// + DrumSteelpan = 0xF56A, + + /// + /// The Font Awesome "drumstick-bite" icon unicode character. + /// + DrumstickBite = 0xF6D7, + + /// + /// The Font Awesome "drupal" icon unicode character. + /// + Drupal = 0xF1A9, + + /// + /// The Font Awesome "dumbbell" icon unicode character. + /// + Dumbbell = 0xF44B, + + /// + /// The Font Awesome "dumpster" icon unicode character. + /// + Dumpster = 0xF793, + + /// + /// The Font Awesome "dumpster-fire" icon unicode character. + /// + DumpsterFire = 0xF794, + + /// + /// The Font Awesome "dungeon" icon unicode character. + /// + Dungeon = 0xF6D9, + + /// + /// The Font Awesome "dyalog" icon unicode character. + /// + Dyalog = 0xF399, + + /// + /// The Font Awesome "earlybirds" icon unicode character. + /// + Earlybirds = 0xF39A, + + /// + /// The Font Awesome "ebay" icon unicode character. + /// + Ebay = 0xF4F4, + + /// + /// The Font Awesome "edge" icon unicode character. + /// + Edge = 0xF282, + + /// + /// The Font Awesome "edit" icon unicode character. + /// + Edit = 0xF044, + + /// + /// The Font Awesome "egg" icon unicode character. + /// + Egg = 0xF7FB, + + /// + /// The Font Awesome "eject" icon unicode character. + /// + Eject = 0xF052, + + /// + /// The Font Awesome "elementor" icon unicode character. + /// + Elementor = 0xF43, + + /// + /// The Font Awesome "ellipsis-h" icon unicode character. + /// + EllipsisH = 0xF141, + + /// + /// The Font Awesome "ellipsis-v" icon unicode character. + /// + EllipsisV = 0xF142, + + /// + /// The Font Awesome "ello" icon unicode character. + /// + Ello = 0xF5F1, + + /// + /// The Font Awesome "ember" icon unicode character. + /// + Ember = 0xF423, + + /// + /// The Font Awesome "empire" icon unicode character. + /// + Empire = 0xF1D1, + + /// + /// The Font Awesome "envelope" icon unicode character. + /// + Envelope = 0xF0E, + + /// + /// The Font Awesome "envelope-open" icon unicode character. + /// + EnvelopeOpen = 0xF2B6, + + /// + /// The Font Awesome "envelope-open-text" icon unicode character. + /// + EnvelopeOpenText = 0xF658, + + /// + /// The Font Awesome "envelope-square" icon unicode character. + /// + EnvelopeSquare = 0xF199, + + /// + /// The Font Awesome "envira" icon unicode character. + /// + Envira = 0xF299, + + /// + /// The Font Awesome "equals" icon unicode character. + /// + Equals = 0xF52C, + + /// + /// The Font Awesome "eraser" icon unicode character. + /// + Eraser = 0xF12D, + + /// + /// The Font Awesome "erlang" icon unicode character. + /// + Erlang = 0xF39D, + + /// + /// The Font Awesome "ethereum" icon unicode character. + /// + Ethereum = 0xF42E, + + /// + /// The Font Awesome "ethernet" icon unicode character. + /// + Ethernet = 0xF796, + + /// + /// The Font Awesome "etsy" icon unicode character. + /// + Etsy = 0xF2D7, + + /// + /// The Font Awesome "euro-sign" icon unicode character. + /// + EuroSign = 0xF153, + + /// + /// The Font Awesome "evernote" icon unicode character. + /// + Evernote = 0xF839, + + /// + /// The Font Awesome "exchange-alt" icon unicode character. + /// + ExchangeAlt = 0xF362, + + /// + /// The Font Awesome "exclamation" icon unicode character. + /// + Exclamation = 0xF12A, + + /// + /// The Font Awesome "exclamation-circle" icon unicode character. + /// + ExclamationCircle = 0xF06A, + + /// + /// The Font Awesome "exclamation-triangle" icon unicode character. + /// + ExclamationTriangle = 0xF071, + + /// + /// The Font Awesome "expand" icon unicode character. + /// + Expand = 0xF065, + + /// + /// The Font Awesome "expand-alt" icon unicode character. + /// + ExpandAlt = 0xF424, + + /// + /// The Font Awesome "expand-arrows-alt" icon unicode character. + /// + ExpandArrowsAlt = 0xF31E, + + /// + /// The Font Awesome "expeditedssl" icon unicode character. + /// + Expeditedssl = 0xF23E, + + /// + /// The Font Awesome "external-link-alt" icon unicode character. + /// + ExternalLinkAlt = 0xF35D, + + /// + /// The Font Awesome "external-link-square-alt" icon unicode character. + /// + ExternalLinkSquareAlt = 0xF36, + + /// + /// The Font Awesome "eye" icon unicode character. + /// + Eye = 0xF06E, + + /// + /// The Font Awesome "eye-dropper" icon unicode character. + /// + EyeDropper = 0xF1FB, + + /// + /// The Font Awesome "eye-slash" icon unicode character. + /// + EyeSlash = 0xF07, + + /// + /// The Font Awesome "facebook" icon unicode character. + /// + Facebook = 0xF09A, + + /// + /// The Font Awesome "facebook-f" icon unicode character. + /// + FacebookF = 0xF39E, + + /// + /// The Font Awesome "facebook-messenger" icon unicode character. + /// + FacebookMessenger = 0xF39F, + + /// + /// The Font Awesome "facebook-square" icon unicode character. + /// + FacebookSquare = 0xF082, + + /// + /// The Font Awesome "fan" icon unicode character. + /// + Fan = 0xF863, + + /// + /// The Font Awesome "fantasy-flight-games" icon unicode character. + /// + FantasyFlightGames = 0xF6DC, + + /// + /// The Font Awesome "fast-backward" icon unicode character. + /// + FastBackward = 0xF049, + + /// + /// The Font Awesome "fast-forward" icon unicode character. + /// + FastForward = 0xF05, + + /// + /// The Font Awesome "fax" icon unicode character. + /// + Fax = 0xF1AC, + + /// + /// The Font Awesome "feather" icon unicode character. + /// + Feather = 0xF52D, + + /// + /// The Font Awesome "feather-alt" icon unicode character. + /// + FeatherAlt = 0xF56B, + + /// + /// The Font Awesome "fedex" icon unicode character. + /// + Fedex = 0xF797, + + /// + /// The Font Awesome "fedora" icon unicode character. + /// + Fedora = 0xF798, + + /// + /// The Font Awesome "female" icon unicode character. + /// + Female = 0xF182, + + /// + /// The Font Awesome "fighter-jet" icon unicode character. + /// + FighterJet = 0xF0FB, + + /// + /// The Font Awesome "figma" icon unicode character. + /// + Figma = 0xF799, + + /// + /// The Font Awesome "file" icon unicode character. + /// + File = 0xF15B, + + /// + /// The Font Awesome "file-alt" icon unicode character. + /// + FileAlt = 0xF15C, + + /// + /// The Font Awesome "file-archive" icon unicode character. + /// + FileArchive = 0xF1C6, + + /// + /// The Font Awesome "file-audio" icon unicode character. + /// + FileAudio = 0xF1C7, + + /// + /// The Font Awesome "file-code" icon unicode character. + /// + FileCode = 0xF1C9, + + /// + /// The Font Awesome "file-contract" icon unicode character. + /// + FileContract = 0xF56C, + + /// + /// The Font Awesome "file-csv" icon unicode character. + /// + FileCsv = 0xF6DD, + + /// + /// The Font Awesome "file-download" icon unicode character. + /// + FileDownload = 0xF56D, + + /// + /// The Font Awesome "file-excel" icon unicode character. + /// + FileExcel = 0xF1C3, + + /// + /// The Font Awesome "file-export" icon unicode character. + /// + FileExport = 0xF56E, + + /// + /// The Font Awesome "file-image" icon unicode character. + /// + FileImage = 0xF1C5, + + /// + /// The Font Awesome "file-import" icon unicode character. + /// + FileImport = 0xF56F, + + /// + /// The Font Awesome "file-invoice" icon unicode character. + /// + FileInvoice = 0xF57, + + /// + /// The Font Awesome "file-invoice-dollar" icon unicode character. + /// + FileInvoiceDollar = 0xF571, + + /// + /// The Font Awesome "file-medical" icon unicode character. + /// + FileMedical = 0xF477, + + /// + /// The Font Awesome "file-medical-alt" icon unicode character. + /// + FileMedicalAlt = 0xF478, + + /// + /// The Font Awesome "file-pdf" icon unicode character. + /// + FilePdf = 0xF1C1, + + /// + /// The Font Awesome "file-powerpoint" icon unicode character. + /// + FilePowerpoint = 0xF1C4, + + /// + /// The Font Awesome "file-prescription" icon unicode character. + /// + FilePrescription = 0xF572, + + /// + /// The Font Awesome "file-signature" icon unicode character. + /// + FileSignature = 0xF573, + + /// + /// The Font Awesome "file-upload" icon unicode character. + /// + FileUpload = 0xF574, + + /// + /// The Font Awesome "file-video" icon unicode character. + /// + FileVideo = 0xF1C8, + + /// + /// The Font Awesome "file-word" icon unicode character. + /// + FileWord = 0xF1C2, + + /// + /// The Font Awesome "fill" icon unicode character. + /// + Fill = 0xF575, + + /// + /// The Font Awesome "fill-drip" icon unicode character. + /// + FillDrip = 0xF576, + + /// + /// The Font Awesome "film" icon unicode character. + /// + Film = 0xF008, + + /// + /// The Font Awesome "filter" icon unicode character. + /// + Filter = 0xF0B, + + /// + /// The Font Awesome "fingerprint" icon unicode character. + /// + Fingerprint = 0xF577, + + /// + /// The Font Awesome "fire" icon unicode character. + /// + Fire = 0xF06D, + + /// + /// The Font Awesome "fire-alt" icon unicode character. + /// + FireAlt = 0xF7E4, + + /// + /// The Font Awesome "fire-extinguisher" icon unicode character. + /// + FireExtinguisher = 0xF134, + + /// + /// The Font Awesome "firefox" icon unicode character. + /// + Firefox = 0xF269, + + /// + /// The Font Awesome "firefox-browser" icon unicode character. + /// + FirefoxBrowser = 0xF907, + + /// + /// The Font Awesome "first-aid" icon unicode character. + /// + FirstAid = 0xF479, + + /// + /// The Font Awesome "firstdraft" icon unicode character. + /// + Firstdraft = 0xF3A1, + + /// + /// The Font Awesome "first-order" icon unicode character. + /// + FirstOrder = 0xF2B, + + /// + /// The Font Awesome "first-order-alt" icon unicode character. + /// + FirstOrderAlt = 0xF50A, + + /// + /// The Font Awesome "fish" icon unicode character. + /// + Fish = 0xF578, + + /// + /// The Font Awesome "fist-raised" icon unicode character. + /// + FistRaised = 0xF6DE, + + /// + /// The Font Awesome "flag" icon unicode character. + /// + Flag = 0xF024, + + /// + /// The Font Awesome "flag-checkered" icon unicode character. + /// + FlagCheckered = 0xF11E, + + /// + /// The Font Awesome "flag-usa" icon unicode character. + /// + FlagUsa = 0xF74D, + + /// + /// The Font Awesome "flask" icon unicode character. + /// + Flask = 0xF0C3, + + /// + /// The Font Awesome "flickr" icon unicode character. + /// + Flickr = 0xF16E, + + /// + /// The Font Awesome "flipboard" icon unicode character. + /// + Flipboard = 0xF44D, + + /// + /// The Font Awesome "flushed" icon unicode character. + /// + Flushed = 0xF579, + + /// + /// The Font Awesome "fly" icon unicode character. + /// + Fly = 0xF417, + + /// + /// The Font Awesome "folder" icon unicode character. + /// + Folder = 0xF07B, + + /// + /// The Font Awesome "folder-minus" icon unicode character. + /// + FolderMinus = 0xF65D, + + /// + /// The Font Awesome "folder-open" icon unicode character. + /// + FolderOpen = 0xF07C, + + /// + /// The Font Awesome "folder-plus" icon unicode character. + /// + FolderPlus = 0xF65E, + + /// + /// The Font Awesome "font" icon unicode character. + /// + Font = 0xF031, + + /// + /// The Font Awesome "font-awesome" icon unicode character. + /// + FontAwesome = 0xF2B4, + + /// + /// The Font Awesome "font-awesome-alt" icon unicode character. + /// + FontAwesomeAlt = 0xF35C, + + /// + /// The Font Awesome "font-awesome-flag" icon unicode character. + /// + FontAwesomeFlag = 0xF425, + + /// + /// The Font Awesome "font-awesome-logo-full" icon unicode character. + /// + FontAwesomeLogoFull = 0xF4E6, + + /// + /// The Font Awesome "fonticons" icon unicode character. + /// + Fonticons = 0xF28, + + /// + /// The Font Awesome "fonticons-fi" icon unicode character. + /// + FonticonsFi = 0xF3A2, + + /// + /// The Font Awesome "football-ball" icon unicode character. + /// + FootballBall = 0xF44E, + + /// + /// The Font Awesome "fort-awesome" icon unicode character. + /// + FortAwesome = 0xF286, + + /// + /// The Font Awesome "fort-awesome-alt" icon unicode character. + /// + FortAwesomeAlt = 0xF3A3, + + /// + /// The Font Awesome "forumbee" icon unicode character. + /// + Forumbee = 0xF211, + + /// + /// The Font Awesome "forward" icon unicode character. + /// + Forward = 0xF04E, + + /// + /// The Font Awesome "foursquare" icon unicode character. + /// + Foursquare = 0xF18, + + /// + /// The Font Awesome "freebsd" icon unicode character. + /// + Freebsd = 0xF3A4, + + /// + /// The Font Awesome "free-code-camp" icon unicode character. + /// + FreeCodeCamp = 0xF2C5, + + /// + /// The Font Awesome "frog" icon unicode character. + /// + Frog = 0xF52E, + + /// + /// The Font Awesome "frown" icon unicode character. + /// + Frown = 0xF119, + + /// + /// The Font Awesome "frown-open" icon unicode character. + /// + FrownOpen = 0xF57A, + + /// + /// The Font Awesome "fulcrum" icon unicode character. + /// + Fulcrum = 0xF50B, + + /// + /// The Font Awesome "funnel-dollar" icon unicode character. + /// + FunnelDollar = 0xF662, + + /// + /// The Font Awesome "futbol" icon unicode character. + /// + Futbol = 0xF1E3, + + /// + /// The Font Awesome "galactic-republic" icon unicode character. + /// + GalacticRepublic = 0xF50C, + + /// + /// The Font Awesome "galactic-senate" icon unicode character. + /// + GalacticSenate = 0xF50D, + + /// + /// The Font Awesome "gamepad" icon unicode character. + /// + Gamepad = 0xF11B, + + /// + /// The Font Awesome "gas-pump" icon unicode character. + /// + GasPump = 0xF52F, + + /// + /// The Font Awesome "gavel" icon unicode character. + /// + Gavel = 0xF0E3, + + /// + /// The Font Awesome "gem" icon unicode character. + /// + Gem = 0xF3A5, + + /// + /// The Font Awesome "genderless" icon unicode character. + /// + Genderless = 0xF22D, + + /// + /// The Font Awesome "get-pocket" icon unicode character. + /// + GetPocket = 0xF265, + + /// + /// The Font Awesome "gg" icon unicode character. + /// + Gg = 0xF26, + + /// + /// The Font Awesome "gg-circle" icon unicode character. + /// + GgCircle = 0xF261, + + /// + /// The Font Awesome "ghost" icon unicode character. + /// + Ghost = 0xF6E2, + + /// + /// The Font Awesome "gift" icon unicode character. + /// + Gift = 0xF06B, + + /// + /// The Font Awesome "gifts" icon unicode character. + /// + Gifts = 0xF79C, + + /// + /// The Font Awesome "git" icon unicode character. + /// + Git = 0xF1D3, + + /// + /// The Font Awesome "git-alt" icon unicode character. + /// + GitAlt = 0xF841, + + /// + /// The Font Awesome "github" icon unicode character. + /// + Github = 0xF09B, + + /// + /// The Font Awesome "github-alt" icon unicode character. + /// + GithubAlt = 0xF113, + + /// + /// The Font Awesome "github-square" icon unicode character. + /// + GithubSquare = 0xF092, + + /// + /// The Font Awesome "gitkraken" icon unicode character. + /// + Gitkraken = 0xF3A6, + + /// + /// The Font Awesome "gitlab" icon unicode character. + /// + Gitlab = 0xF296, + + /// + /// The Font Awesome "git-square" icon unicode character. + /// + GitSquare = 0xF1D2, + + /// + /// The Font Awesome "gitter" icon unicode character. + /// + Gitter = 0xF426, + + /// + /// The Font Awesome "glass-cheers" icon unicode character. + /// + GlassCheers = 0xF79F, + + /// + /// The Font Awesome "glasses" icon unicode character. + /// + Glasses = 0xF53, + + /// + /// The Font Awesome "glass-martini" icon unicode character. + /// + GlassMartini = 0xF, + + /// + /// The Font Awesome "glass-martini-alt" icon unicode character. + /// + GlassMartiniAlt = 0xF57B, + + /// + /// The Font Awesome "glass-whiskey" icon unicode character. + /// + GlassWhiskey = 0xF7A, + + /// + /// The Font Awesome "glide" icon unicode character. + /// + Glide = 0xF2A5, + + /// + /// The Font Awesome "glide-g" icon unicode character. + /// + GlideG = 0xF2A6, + + /// + /// The Font Awesome "globe" icon unicode character. + /// + Globe = 0xF0AC, + + /// + /// The Font Awesome "globe-africa" icon unicode character. + /// + GlobeAfrica = 0xF57C, + + /// + /// The Font Awesome "globe-americas" icon unicode character. + /// + GlobeAmericas = 0xF57D, + + /// + /// The Font Awesome "globe-asia" icon unicode character. + /// + GlobeAsia = 0xF57E, + + /// + /// The Font Awesome "globe-europe" icon unicode character. + /// + GlobeEurope = 0xF7A2, + + /// + /// The Font Awesome "gofore" icon unicode character. + /// + Gofore = 0xF3A7, + + /// + /// The Font Awesome "golf-ball" icon unicode character. + /// + GolfBall = 0xF45, + + /// + /// The Font Awesome "goodreads" icon unicode character. + /// + Goodreads = 0xF3A8, + + /// + /// The Font Awesome "goodreads-g" icon unicode character. + /// + GoodreadsG = 0xF3A9, + + /// + /// The Font Awesome "google" icon unicode character. + /// + Google = 0xF1A, + + /// + /// The Font Awesome "google-drive" icon unicode character. + /// + GoogleDrive = 0xF3AA, + + /// + /// The Font Awesome "google-play" icon unicode character. + /// + GooglePlay = 0xF3AB, + + /// + /// The Font Awesome "google-plus" icon unicode character. + /// + GooglePlus = 0xF2B3, + + /// + /// The Font Awesome "google-plus-g" icon unicode character. + /// + GooglePlusG = 0xF0D5, + + /// + /// The Font Awesome "google-plus-square" icon unicode character. + /// + GooglePlusSquare = 0xF0D4, + + /// + /// The Font Awesome "google-wallet" icon unicode character. + /// + GoogleWallet = 0xF1EE, + + /// + /// The Font Awesome "gopuram" icon unicode character. + /// + Gopuram = 0xF664, + + /// + /// The Font Awesome "graduation-cap" icon unicode character. + /// + GraduationCap = 0xF19D, + + /// + /// The Font Awesome "gratipay" icon unicode character. + /// + Gratipay = 0xF184, + + /// + /// The Font Awesome "grav" icon unicode character. + /// + Grav = 0xF2D6, + + /// + /// The Font Awesome "greater-than" icon unicode character. + /// + GreaterThan = 0xF531, + + /// + /// The Font Awesome "greater-than-equal" icon unicode character. + /// + GreaterThanEqual = 0xF532, + + /// + /// The Font Awesome "grimace" icon unicode character. + /// + Grimace = 0xF57F, + + /// + /// The Font Awesome "grin" icon unicode character. + /// + Grin = 0xF58, + + /// + /// The Font Awesome "grin-alt" icon unicode character. + /// + GrinAlt = 0xF581, + + /// + /// The Font Awesome "grin-beam" icon unicode character. + /// + GrinBeam = 0xF582, + + /// + /// The Font Awesome "grin-beam-sweat" icon unicode character. + /// + GrinBeamSweat = 0xF583, + + /// + /// The Font Awesome "grin-hearts" icon unicode character. + /// + GrinHearts = 0xF584, + + /// + /// The Font Awesome "grin-squint" icon unicode character. + /// + GrinSquint = 0xF585, + + /// + /// The Font Awesome "grin-squint-tears" icon unicode character. + /// + GrinSquintTears = 0xF586, + + /// + /// The Font Awesome "grin-stars" icon unicode character. + /// + GrinStars = 0xF587, + + /// + /// The Font Awesome "grin-tears" icon unicode character. + /// + GrinTears = 0xF588, + + /// + /// The Font Awesome "grin-tongue" icon unicode character. + /// + GrinTongue = 0xF589, + + /// + /// The Font Awesome "grin-tongue-squint" icon unicode character. + /// + GrinTongueSquint = 0xF58A, + + /// + /// The Font Awesome "grin-tongue-wink" icon unicode character. + /// + GrinTongueWink = 0xF58B, + + /// + /// The Font Awesome "grin-wink" icon unicode character. + /// + GrinWink = 0xF58C, + + /// + /// The Font Awesome "gripfire" icon unicode character. + /// + Gripfire = 0xF3AC, + + /// + /// The Font Awesome "grip-horizontal" icon unicode character. + /// + GripHorizontal = 0xF58D, + + /// + /// The Font Awesome "grip-lines" icon unicode character. + /// + GripLines = 0xF7A4, + + /// + /// The Font Awesome "grip-lines-vertical" icon unicode character. + /// + GripLinesVertical = 0xF7A5, + + /// + /// The Font Awesome "grip-vertical" icon unicode character. + /// + GripVertical = 0xF58E, + + /// + /// The Font Awesome "grunt" icon unicode character. + /// + Grunt = 0xF3AD, + + /// + /// The Font Awesome "guitar" icon unicode character. + /// + Guitar = 0xF7A6, + + /// + /// The Font Awesome "gulp" icon unicode character. + /// + Gulp = 0xF3AE, + + /// + /// The Font Awesome "hacker-news" icon unicode character. + /// + HackerNews = 0xF1D4, + + /// + /// The Font Awesome "hacker-news-square" icon unicode character. + /// + HackerNewsSquare = 0xF3AF, + + /// + /// The Font Awesome "hackerrank" icon unicode character. + /// + Hackerrank = 0xF5F7, + + /// + /// The Font Awesome "hamburger" icon unicode character. + /// + Hamburger = 0xF805, + + /// + /// The Font Awesome "hammer" icon unicode character. + /// + Hammer = 0xF6E3, + + /// + /// The Font Awesome "hamsa" icon unicode character. + /// + Hamsa = 0xF665, + + /// + /// The Font Awesome "hand-holding" icon unicode character. + /// + HandHolding = 0xF4BD, + + /// + /// The Font Awesome "hand-holding-heart" icon unicode character. + /// + HandHoldingHeart = 0xF4BE, + + /// + /// The Font Awesome "hand-holding-usd" icon unicode character. + /// + HandHoldingUsd = 0xF4C, + + /// + /// The Font Awesome "hand-lizard" icon unicode character. + /// + HandLizard = 0xF258, + + /// + /// The Font Awesome "hand-middle-finger" icon unicode character. + /// + HandMiddleFinger = 0xF806, + + /// + /// The Font Awesome "hand-paper" icon unicode character. + /// + HandPaper = 0xF256, + + /// + /// The Font Awesome "hand-peace" icon unicode character. + /// + HandPeace = 0xF25B, + + /// + /// The Font Awesome "hand-point-down" icon unicode character. + /// + HandPointDown = 0xF0A7, + + /// + /// The Font Awesome "hand-pointer" icon unicode character. + /// + HandPointer = 0xF25A, + + /// + /// The Font Awesome "hand-point-left" icon unicode character. + /// + HandPointLeft = 0xF0A5, + + /// + /// The Font Awesome "hand-point-right" icon unicode character. + /// + HandPointRight = 0xF0A4, + + /// + /// The Font Awesome "hand-point-up" icon unicode character. + /// + HandPointUp = 0xF0A6, + + /// + /// The Font Awesome "hand-rock" icon unicode character. + /// + HandRock = 0xF255, + + /// + /// The Font Awesome "hands" icon unicode character. + /// + Hands = 0xF4C2, + + /// + /// The Font Awesome "hand-scissors" icon unicode character. + /// + HandScissors = 0xF257, + + /// + /// The Font Awesome "handshake" icon unicode character. + /// + Handshake = 0xF2B5, + + /// + /// The Font Awesome "hands-helping" icon unicode character. + /// + HandsHelping = 0xF4C4, + + /// + /// The Font Awesome "hand-spock" icon unicode character. + /// + HandSpock = 0xF259, + + /// + /// The Font Awesome "hanukiah" icon unicode character. + /// + Hanukiah = 0xF6E6, + + /// + /// The Font Awesome "hard-hat" icon unicode character. + /// + HardHat = 0xF807, + + /// + /// The Font Awesome "hashtag" icon unicode character. + /// + Hashtag = 0xF292, + + /// + /// The Font Awesome "hat-cowboy" icon unicode character. + /// + HatCowboy = 0xF8C, + + /// + /// The Font Awesome "hat-cowboy-side" icon unicode character. + /// + HatCowboySide = 0xF8C1, + + /// + /// The Font Awesome "hat-wizard" icon unicode character. + /// + HatWizard = 0xF6E8, + + /// + /// The Font Awesome "hdd" icon unicode character. + /// + Hdd = 0xF0A, + + /// + /// The Font Awesome "heading" icon unicode character. + /// + Heading = 0xF1DC, + + /// + /// The Font Awesome "headphones" icon unicode character. + /// + Headphones = 0xF025, + + /// + /// The Font Awesome "headphones-alt" icon unicode character. + /// + HeadphonesAlt = 0xF58F, + + /// + /// The Font Awesome "headset" icon unicode character. + /// + Headset = 0xF59, + + /// + /// The Font Awesome "heart" icon unicode character. + /// + Heart = 0xF004, + + /// + /// The Font Awesome "heartbeat" icon unicode character. + /// + Heartbeat = 0xF21E, + + /// + /// The Font Awesome "heart-broken" icon unicode character. + /// + HeartBroken = 0xF7A9, + + /// + /// The Font Awesome "helicopter" icon unicode character. + /// + Helicopter = 0xF533, + + /// + /// The Font Awesome "highlighter" icon unicode character. + /// + Highlighter = 0xF591, + + /// + /// The Font Awesome "hiking" icon unicode character. + /// + Hiking = 0xF6EC, + + /// + /// The Font Awesome "hippo" icon unicode character. + /// + Hippo = 0xF6ED, + + /// + /// The Font Awesome "hips" icon unicode character. + /// + Hips = 0xF452, + + /// + /// The Font Awesome "hire-a-helper" icon unicode character. + /// + HireAHelper = 0xF3B, + + /// + /// The Font Awesome "history" icon unicode character. + /// + History = 0xF1DA, + + /// + /// The Font Awesome "hockey-puck" icon unicode character. + /// + HockeyPuck = 0xF453, + + /// + /// The Font Awesome "holly-berry" icon unicode character. + /// + HollyBerry = 0xF7AA, + + /// + /// The Font Awesome "home" icon unicode character. + /// + Home = 0xF015, + + /// + /// The Font Awesome "hooli" icon unicode character. + /// + Hooli = 0xF427, + + /// + /// The Font Awesome "hornbill" icon unicode character. + /// + Hornbill = 0xF592, + + /// + /// The Font Awesome "horse" icon unicode character. + /// + Horse = 0xF6F, + + /// + /// The Font Awesome "horse-head" icon unicode character. + /// + HorseHead = 0xF7AB, + + /// + /// The Font Awesome "hospital" icon unicode character. + /// + Hospital = 0xF0F8, + + /// + /// The Font Awesome "hospital-alt" icon unicode character. + /// + HospitalAlt = 0xF47D, + + /// + /// The Font Awesome "hospital-symbol" icon unicode character. + /// + HospitalSymbol = 0xF47E, + + /// + /// The Font Awesome "hotdog" icon unicode character. + /// + Hotdog = 0xF80F, + + /// + /// The Font Awesome "hotel" icon unicode character. + /// + Hotel = 0xF594, + + /// + /// The Font Awesome "hotjar" icon unicode character. + /// + Hotjar = 0xF3B1, + + /// + /// The Font Awesome "hot-tub" icon unicode character. + /// + HotTub = 0xF593, + + /// + /// The Font Awesome "hourglass" icon unicode character. + /// + Hourglass = 0xF254, + + /// + /// The Font Awesome "hourglass-end" icon unicode character. + /// + HourglassEnd = 0xF253, + + /// + /// The Font Awesome "hourglass-half" icon unicode character. + /// + HourglassHalf = 0xF252, + + /// + /// The Font Awesome "hourglass-start" icon unicode character. + /// + HourglassStart = 0xF251, + + /// + /// The Font Awesome "house-damage" icon unicode character. + /// + HouseDamage = 0xF6F1, + + /// + /// The Font Awesome "houzz" icon unicode character. + /// + Houzz = 0xF27C, + + /// + /// The Font Awesome "hryvnia" icon unicode character. + /// + Hryvnia = 0xF6F2, + + /// + /// The Font Awesome "h-square" icon unicode character. + /// + HSquare = 0xF0FD, + + /// + /// The Font Awesome "html5" icon unicode character. + /// + Html5 = 0xF13B, + + /// + /// The Font Awesome "hubspot" icon unicode character. + /// + Hubspot = 0xF3B2, + + /// + /// The Font Awesome "ice-cream" icon unicode character. + /// + IceCream = 0xF81, + + /// + /// The Font Awesome "icicles" icon unicode character. + /// + Icicles = 0xF7AD, + + /// + /// The Font Awesome "icons" icon unicode character. + /// + Icons = 0xF86D, + + /// + /// The Font Awesome "i-cursor" icon unicode character. + /// + ICursor = 0xF246, + + /// + /// The Font Awesome "id-badge" icon unicode character. + /// + IdBadge = 0xF2C1, + + /// + /// The Font Awesome "id-card" icon unicode character. + /// + IdCard = 0xF2C2, + + /// + /// The Font Awesome "id-card-alt" icon unicode character. + /// + IdCardAlt = 0xF47F, + + /// + /// The Font Awesome "ideal" icon unicode character. + /// + Ideal = 0xF913, + + /// + /// The Font Awesome "igloo" icon unicode character. + /// + Igloo = 0xF7AE, + + /// + /// The Font Awesome "image" icon unicode character. + /// + Image = 0xF03E, + + /// + /// The Font Awesome "images" icon unicode character. + /// + Images = 0xF302, + + /// + /// The Font Awesome "imdb" icon unicode character. + /// + Imdb = 0xF2D8, + + /// + /// The Font Awesome "inbox" icon unicode character. + /// + Inbox = 0xF01C, + + /// + /// The Font Awesome "indent" icon unicode character. + /// + Indent = 0xF03C, + + /// + /// The Font Awesome "industry" icon unicode character. + /// + Industry = 0xF275, + + /// + /// The Font Awesome "infinity" icon unicode character. + /// + Infinity = 0xF534, + + /// + /// The Font Awesome "info" icon unicode character. + /// + Info = 0xF129, + + /// + /// The Font Awesome "info-circle" icon unicode character. + /// + InfoCircle = 0xF05A, + + /// + /// The Font Awesome "instagram" icon unicode character. + /// + Instagram = 0xF16D, + + /// + /// The Font Awesome "instagram-square" icon unicode character. + /// + InstagramSquare = 0xF955, + + /// + /// The Font Awesome "intercom" icon unicode character. + /// + Intercom = 0xF7AF, + + /// + /// The Font Awesome "internet-explorer" icon unicode character. + /// + InternetExplorer = 0xF26B, + + /// + /// The Font Awesome "invision" icon unicode character. + /// + Invision = 0xF7B, + + /// + /// The Font Awesome "ioxhost" icon unicode character. + /// + Ioxhost = 0xF208, + + /// + /// The Font Awesome "italic" icon unicode character. + /// + Italic = 0xF033, + + /// + /// The Font Awesome "itch-io" icon unicode character. + /// + ItchIo = 0xF83A, + + /// + /// The Font Awesome "itunes" icon unicode character. + /// + Itunes = 0xF3B4, + + /// + /// The Font Awesome "itunes-note" icon unicode character. + /// + ItunesNote = 0xF3B5, + + /// + /// The Font Awesome "java" icon unicode character. + /// + Java = 0xF4E4, + + /// + /// The Font Awesome "jedi" icon unicode character. + /// + Jedi = 0xF669, + + /// + /// The Font Awesome "jedi-order" icon unicode character. + /// + JediOrder = 0xF50E, + + /// + /// The Font Awesome "jenkins" icon unicode character. + /// + Jenkins = 0xF3B6, + + /// + /// The Font Awesome "jira" icon unicode character. + /// + Jira = 0xF7B1, + + /// + /// The Font Awesome "joget" icon unicode character. + /// + Joget = 0xF3B7, + + /// + /// The Font Awesome "joint" icon unicode character. + /// + Joint = 0xF595, + + /// + /// The Font Awesome "joomla" icon unicode character. + /// + Joomla = 0xF1AA, + + /// + /// The Font Awesome "journal-whills" icon unicode character. + /// + JournalWhills = 0xF66A, + + /// + /// The Font Awesome "js" icon unicode character. + /// + Js = 0xF3B8, + + /// + /// The Font Awesome "jsfiddle" icon unicode character. + /// + Jsfiddle = 0xF1CC, + + /// + /// The Font Awesome "js-square" icon unicode character. + /// + JsSquare = 0xF3B9, + + /// + /// The Font Awesome "kaaba" icon unicode character. + /// + Kaaba = 0xF66B, + + /// + /// The Font Awesome "kaggle" icon unicode character. + /// + Kaggle = 0xF5FA, + + /// + /// The Font Awesome "key" icon unicode character. + /// + Key = 0xF084, + + /// + /// The Font Awesome "keybase" icon unicode character. + /// + Keybase = 0xF4F5, + + /// + /// The Font Awesome "keyboard" icon unicode character. + /// + Keyboard = 0xF11C, + + /// + /// The Font Awesome "keycdn" icon unicode character. + /// + Keycdn = 0xF3BA, + + /// + /// The Font Awesome "khanda" icon unicode character. + /// + Khanda = 0xF66D, + + /// + /// The Font Awesome "kickstarter" icon unicode character. + /// + Kickstarter = 0xF3BB, + + /// + /// The Font Awesome "kickstarter-k" icon unicode character. + /// + KickstarterK = 0xF3BC, + + /// + /// The Font Awesome "kiss" icon unicode character. + /// + Kiss = 0xF596, + + /// + /// The Font Awesome "kiss-beam" icon unicode character. + /// + KissBeam = 0xF597, + + /// + /// The Font Awesome "kiss-wink-heart" icon unicode character. + /// + KissWinkHeart = 0xF598, + + /// + /// The Font Awesome "kiwi-bird" icon unicode character. + /// + KiwiBird = 0xF535, + + /// + /// The Font Awesome "korvue" icon unicode character. + /// + Korvue = 0xF42F, + + /// + /// The Font Awesome "landmark" icon unicode character. + /// + Landmark = 0xF66F, + + /// + /// The Font Awesome "language" icon unicode character. + /// + Language = 0xF1AB, + + /// + /// The Font Awesome "laptop" icon unicode character. + /// + Laptop = 0xF109, + + /// + /// The Font Awesome "laptop-code" icon unicode character. + /// + LaptopCode = 0xF5FC, + + /// + /// The Font Awesome "laptop-medical" icon unicode character. + /// + LaptopMedical = 0xF812, + + /// + /// The Font Awesome "laravel" icon unicode character. + /// + Laravel = 0xF3BD, + + /// + /// The Font Awesome "lastfm" icon unicode character. + /// + Lastfm = 0xF202, + + /// + /// The Font Awesome "lastfm-square" icon unicode character. + /// + LastfmSquare = 0xF203, + + /// + /// The Font Awesome "laugh" icon unicode character. + /// + Laugh = 0xF599, + + /// + /// The Font Awesome "laugh-beam" icon unicode character. + /// + LaughBeam = 0xF59A, + + /// + /// The Font Awesome "laugh-squint" icon unicode character. + /// + LaughSquint = 0xF59B, + + /// + /// The Font Awesome "laugh-wink" icon unicode character. + /// + LaughWink = 0xF59C, + + /// + /// The Font Awesome "layer-group" icon unicode character. + /// + LayerGroup = 0xF5FD, + + /// + /// The Font Awesome "leaf" icon unicode character. + /// + Leaf = 0xF06C, + + /// + /// The Font Awesome "leanpub" icon unicode character. + /// + Leanpub = 0xF212, + + /// + /// The Font Awesome "lemon" icon unicode character. + /// + Lemon = 0xF094, + + /// + /// The Font Awesome "less" icon unicode character. + /// + Less = 0xF41D, + + /// + /// The Font Awesome "less-than" icon unicode character. + /// + LessThan = 0xF536, + + /// + /// The Font Awesome "less-than-equal" icon unicode character. + /// + LessThanEqual = 0xF537, + + /// + /// The Font Awesome "level-down-alt" icon unicode character. + /// + LevelDownAlt = 0xF3BE, + + /// + /// The Font Awesome "level-up-alt" icon unicode character. + /// + LevelUpAlt = 0xF3BF, + + /// + /// The Font Awesome "life-ring" icon unicode character. + /// + LifeRing = 0xF1CD, + + /// + /// The Font Awesome "lightbulb" icon unicode character. + /// + Lightbulb = 0xF0EB, + + /// + /// The Font Awesome "line" icon unicode character. + /// + Line = 0xF3C, + + /// + /// The Font Awesome "link" icon unicode character. + /// + Link = 0xF0C1, + + /// + /// The Font Awesome "linkedin" icon unicode character. + /// + Linkedin = 0xF08C, + + /// + /// The Font Awesome "linkedin-in" icon unicode character. + /// + LinkedinIn = 0xF0E1, + + /// + /// The Font Awesome "linode" icon unicode character. + /// + Linode = 0xF2B8, + + /// + /// The Font Awesome "linux" icon unicode character. + /// + Linux = 0xF17C, + + /// + /// The Font Awesome "lira-sign" icon unicode character. + /// + LiraSign = 0xF195, + + /// + /// The Font Awesome "list" icon unicode character. + /// + List = 0xF03A, + + /// + /// The Font Awesome "list-alt" icon unicode character. + /// + ListAlt = 0xF022, + + /// + /// The Font Awesome "list-ol" icon unicode character. + /// + ListOl = 0xF0CB, + + /// + /// The Font Awesome "list-ul" icon unicode character. + /// + ListUl = 0xF0CA, + + /// + /// The Font Awesome "location-arrow" icon unicode character. + /// + LocationArrow = 0xF124, + + /// + /// The Font Awesome "lock" icon unicode character. + /// + Lock = 0xF023, + + /// + /// The Font Awesome "lock-open" icon unicode character. + /// + LockOpen = 0xF3C1, + + /// + /// The Font Awesome "long-arrow-alt-down" icon unicode character. + /// + LongArrowAltDown = 0xF309, + + /// + /// The Font Awesome "long-arrow-alt-left" icon unicode character. + /// + LongArrowAltLeft = 0xF30A, + + /// + /// The Font Awesome "long-arrow-alt-right" icon unicode character. + /// + LongArrowAltRight = 0xF30B, + + /// + /// The Font Awesome "long-arrow-alt-up" icon unicode character. + /// + LongArrowAltUp = 0xF30C, + + /// + /// The Font Awesome "low-vision" icon unicode character. + /// + LowVision = 0xF2A8, + + /// + /// The Font Awesome "luggage-cart" icon unicode character. + /// + LuggageCart = 0xF59D, + + /// + /// The Font Awesome "lyft" icon unicode character. + /// + Lyft = 0xF3C3, + + /// + /// The Font Awesome "magento" icon unicode character. + /// + Magento = 0xF3C4, + + /// + /// The Font Awesome "magic" icon unicode character. + /// + Magic = 0xF0D, + + /// + /// The Font Awesome "magnet" icon unicode character. + /// + Magnet = 0xF076, + + /// + /// The Font Awesome "mail-bulk" icon unicode character. + /// + MailBulk = 0xF674, + + /// + /// The Font Awesome "mailchimp" icon unicode character. + /// + Mailchimp = 0xF59E, + + /// + /// The Font Awesome "male" icon unicode character. + /// + Male = 0xF183, + + /// + /// The Font Awesome "mandalorian" icon unicode character. + /// + Mandalorian = 0xF50F, + + /// + /// The Font Awesome "map" icon unicode character. + /// + Map = 0xF279, + + /// + /// The Font Awesome "map-marked" icon unicode character. + /// + MapMarked = 0xF59F, + + /// + /// The Font Awesome "map-marked-alt" icon unicode character. + /// + MapMarkedAlt = 0xF5A, + + /// + /// The Font Awesome "map-marker" icon unicode character. + /// + MapMarker = 0xF041, + + /// + /// The Font Awesome "map-marker-alt" icon unicode character. + /// + MapMarkerAlt = 0xF3C5, + + /// + /// The Font Awesome "map-pin" icon unicode character. + /// + MapPin = 0xF276, + + /// + /// The Font Awesome "map-signs" icon unicode character. + /// + MapSigns = 0xF277, + + /// + /// The Font Awesome "markdown" icon unicode character. + /// + Markdown = 0xF60F, + + /// + /// The Font Awesome "marker" icon unicode character. + /// + Marker = 0xF5A1, + + /// + /// The Font Awesome "mars" icon unicode character. + /// + Mars = 0xF222, + + /// + /// The Font Awesome "mars-double" icon unicode character. + /// + MarsDouble = 0xF227, + + /// + /// The Font Awesome "mars-stroke" icon unicode character. + /// + MarsStroke = 0xF229, + + /// + /// The Font Awesome "mars-stroke-h" icon unicode character. + /// + MarsStrokeH = 0xF22B, + + /// + /// The Font Awesome "mars-stroke-v" icon unicode character. + /// + MarsStrokeV = 0xF22A, + + /// + /// The Font Awesome "mask" icon unicode character. + /// + Mask = 0xF6FA, + + /// + /// The Font Awesome "mastodon" icon unicode character. + /// + Mastodon = 0xF4F6, + + /// + /// The Font Awesome "maxcdn" icon unicode character. + /// + Maxcdn = 0xF136, + + /// + /// The Font Awesome "mdb" icon unicode character. + /// + Mdb = 0xF8CA, + + /// + /// The Font Awesome "medal" icon unicode character. + /// + Medal = 0xF5A2, + + /// + /// The Font Awesome "medapps" icon unicode character. + /// + Medapps = 0xF3C6, + + /// + /// The Font Awesome "medium" icon unicode character. + /// + Medium = 0xF23A, + + /// + /// The Font Awesome "medium-m" icon unicode character. + /// + MediumM = 0xF3C7, + + /// + /// The Font Awesome "medkit" icon unicode character. + /// + Medkit = 0xF0FA, + + /// + /// The Font Awesome "medrt" icon unicode character. + /// + Medrt = 0xF3C8, + + /// + /// The Font Awesome "meetup" icon unicode character. + /// + Meetup = 0xF2E, + + /// + /// The Font Awesome "megaport" icon unicode character. + /// + Megaport = 0xF5A3, + + /// + /// The Font Awesome "meh" icon unicode character. + /// + Meh = 0xF11A, + + /// + /// The Font Awesome "meh-blank" icon unicode character. + /// + MehBlank = 0xF5A4, + + /// + /// The Font Awesome "meh-rolling-eyes" icon unicode character. + /// + MehRollingEyes = 0xF5A5, + + /// + /// The Font Awesome "memory" icon unicode character. + /// + Memory = 0xF538, + + /// + /// The Font Awesome "mendeley" icon unicode character. + /// + Mendeley = 0xF7B3, + + /// + /// The Font Awesome "menorah" icon unicode character. + /// + Menorah = 0xF676, + + /// + /// The Font Awesome "mercury" icon unicode character. + /// + Mercury = 0xF223, + + /// + /// The Font Awesome "meteor" icon unicode character. + /// + Meteor = 0xF753, + + /// + /// The Font Awesome "microblog" icon unicode character. + /// + Microblog = 0xF91A, + + /// + /// The Font Awesome "microchip" icon unicode character. + /// + Microchip = 0xF2DB, + + /// + /// The Font Awesome "microphone" icon unicode character. + /// + Microphone = 0xF13, + + /// + /// The Font Awesome "microphone-alt" icon unicode character. + /// + MicrophoneAlt = 0xF3C9, + + /// + /// The Font Awesome "microphone-alt-slash" icon unicode character. + /// + MicrophoneAltSlash = 0xF539, + + /// + /// The Font Awesome "microphone-slash" icon unicode character. + /// + MicrophoneSlash = 0xF131, + + /// + /// The Font Awesome "microscope" icon unicode character. + /// + Microscope = 0xF61, + + /// + /// The Font Awesome "microsoft" icon unicode character. + /// + Microsoft = 0xF3CA, + + /// + /// The Font Awesome "minus" icon unicode character. + /// + Minus = 0xF068, + + /// + /// The Font Awesome "minus-circle" icon unicode character. + /// + MinusCircle = 0xF056, + + /// + /// The Font Awesome "minus-square" icon unicode character. + /// + MinusSquare = 0xF146, + + /// + /// The Font Awesome "mitten" icon unicode character. + /// + Mitten = 0xF7B5, + + /// + /// The Font Awesome "mix" icon unicode character. + /// + Mix = 0xF3CB, + + /// + /// The Font Awesome "mixcloud" icon unicode character. + /// + Mixcloud = 0xF289, + + /// + /// The Font Awesome "mixer" icon unicode character. + /// + Mixer = 0xF956, + + /// + /// The Font Awesome "mizuni" icon unicode character. + /// + Mizuni = 0xF3CC, + + /// + /// The Font Awesome "mobile" icon unicode character. + /// + Mobile = 0xF10B, + + /// + /// The Font Awesome "mobile-alt" icon unicode character. + /// + MobileAlt = 0xF3CD, + + /// + /// The Font Awesome "modx" icon unicode character. + /// + Modx = 0xF285, + + /// + /// The Font Awesome "monero" icon unicode character. + /// + Monero = 0xF3D, + + /// + /// The Font Awesome "money-bill" icon unicode character. + /// + MoneyBill = 0xF0D6, + + /// + /// The Font Awesome "money-bill-alt" icon unicode character. + /// + MoneyBillAlt = 0xF3D1, + + /// + /// The Font Awesome "money-bill-wave" icon unicode character. + /// + MoneyBillWave = 0xF53A, + + /// + /// The Font Awesome "money-bill-wave-alt" icon unicode character. + /// + MoneyBillWaveAlt = 0xF53B, + + /// + /// The Font Awesome "money-check" icon unicode character. + /// + MoneyCheck = 0xF53C, + + /// + /// The Font Awesome "money-check-alt" icon unicode character. + /// + MoneyCheckAlt = 0xF53D, + + /// + /// The Font Awesome "monument" icon unicode character. + /// + Monument = 0xF5A6, + + /// + /// The Font Awesome "moon" icon unicode character. + /// + Moon = 0xF186, + + /// + /// The Font Awesome "mortar-pestle" icon unicode character. + /// + MortarPestle = 0xF5A7, + + /// + /// The Font Awesome "mosque" icon unicode character. + /// + Mosque = 0xF678, + + /// + /// The Font Awesome "motorcycle" icon unicode character. + /// + Motorcycle = 0xF21C, + + /// + /// The Font Awesome "mountain" icon unicode character. + /// + Mountain = 0xF6FC, + + /// + /// The Font Awesome "mouse" icon unicode character. + /// + Mouse = 0xF8CC, + + /// + /// The Font Awesome "mouse-pointer" icon unicode character. + /// + MousePointer = 0xF245, + + /// + /// The Font Awesome "mug-hot" icon unicode character. + /// + MugHot = 0xF7B6, + + /// + /// The Font Awesome "music" icon unicode character. + /// + Music = 0xF001, + + /// + /// The Font Awesome "napster" icon unicode character. + /// + Napster = 0xF3D2, + + /// + /// The Font Awesome "neos" icon unicode character. + /// + Neos = 0xF612, + + /// + /// The Font Awesome "network-wired" icon unicode character. + /// + NetworkWired = 0xF6FF, + + /// + /// The Font Awesome "neuter" icon unicode character. + /// + Neuter = 0xF22C, + + /// + /// The Font Awesome "newspaper" icon unicode character. + /// + Newspaper = 0xF1EA, + + /// + /// The Font Awesome "nimblr" icon unicode character. + /// + Nimblr = 0xF5A8, + + /// + /// The Font Awesome "node" icon unicode character. + /// + Node = 0xF419, + + /// + /// The Font Awesome "node-js" icon unicode character. + /// + NodeJs = 0xF3D3, + + /// + /// The Font Awesome "not-equal" icon unicode character. + /// + NotEqual = 0xF53E, + + /// + /// The Font Awesome "notes-medical" icon unicode character. + /// + NotesMedical = 0xF481, + + /// + /// The Font Awesome "npm" icon unicode character. + /// + Npm = 0xF3D4, + + /// + /// The Font Awesome "ns8" icon unicode character. + /// + Ns8 = 0xF3D5, + + /// + /// The Font Awesome "nutritionix" icon unicode character. + /// + Nutritionix = 0xF3D6, + + /// + /// The Font Awesome "object-group" icon unicode character. + /// + ObjectGroup = 0xF247, + + /// + /// The Font Awesome "object-ungroup" icon unicode character. + /// + ObjectUngroup = 0xF248, + + /// + /// The Font Awesome "odnoklassniki" icon unicode character. + /// + Odnoklassniki = 0xF263, + + /// + /// The Font Awesome "odnoklassniki-square" icon unicode character. + /// + OdnoklassnikiSquare = 0xF264, + + /// + /// The Font Awesome "oil-can" icon unicode character. + /// + OilCan = 0xF613, + + /// + /// The Font Awesome "old-republic" icon unicode character. + /// + OldRepublic = 0xF51, + + /// + /// The Font Awesome "om" icon unicode character. + /// + Om = 0xF679, + + /// + /// The Font Awesome "opencart" icon unicode character. + /// + Opencart = 0xF23D, + + /// + /// The Font Awesome "openid" icon unicode character. + /// + Openid = 0xF19B, + + /// + /// The Font Awesome "opera" icon unicode character. + /// + Opera = 0xF26A, + + /// + /// The Font Awesome "optin-monster" icon unicode character. + /// + OptinMonster = 0xF23C, + + /// + /// The Font Awesome "orcid" icon unicode character. + /// + Orcid = 0xF8D2, + + /// + /// The Font Awesome "osi" icon unicode character. + /// + Osi = 0xF41A, + + /// + /// The Font Awesome "otter" icon unicode character. + /// + Otter = 0xF7, + + /// + /// The Font Awesome "outdent" icon unicode character. + /// + Outdent = 0xF03B, + + /// + /// The Font Awesome "page4" icon unicode character. + /// + Page4 = 0xF3D7, + + /// + /// The Font Awesome "pagelines" icon unicode character. + /// + Pagelines = 0xF18C, + + /// + /// The Font Awesome "pager" icon unicode character. + /// + Pager = 0xF815, + + /// + /// The Font Awesome "paint-brush" icon unicode character. + /// + PaintBrush = 0xF1FC, + + /// + /// The Font Awesome "paint-roller" icon unicode character. + /// + PaintRoller = 0xF5AA, + + /// + /// The Font Awesome "palette" icon unicode character. + /// + Palette = 0xF53F, + + /// + /// The Font Awesome "palfed" icon unicode character. + /// + Palfed = 0xF3D8, + + /// + /// The Font Awesome "pallet" icon unicode character. + /// + Pallet = 0xF482, + + /// + /// The Font Awesome "paperclip" icon unicode character. + /// + Paperclip = 0xF0C6, + + /// + /// The Font Awesome "paper-plane" icon unicode character. + /// + PaperPlane = 0xF1D8, + + /// + /// The Font Awesome "parachute-box" icon unicode character. + /// + ParachuteBox = 0xF4CD, + + /// + /// The Font Awesome "paragraph" icon unicode character. + /// + Paragraph = 0xF1DD, + + /// + /// The Font Awesome "parking" icon unicode character. + /// + Parking = 0xF54, + + /// + /// The Font Awesome "passport" icon unicode character. + /// + Passport = 0xF5AB, + + /// + /// The Font Awesome "pastafarianism" icon unicode character. + /// + Pastafarianism = 0xF67B, + + /// + /// The Font Awesome "paste" icon unicode character. + /// + Paste = 0xF0EA, + + /// + /// The Font Awesome "patreon" icon unicode character. + /// + Patreon = 0xF3D9, + + /// + /// The Font Awesome "pause" icon unicode character. + /// + Pause = 0xF04C, + + /// + /// The Font Awesome "pause-circle" icon unicode character. + /// + PauseCircle = 0xF28B, + + /// + /// The Font Awesome "paw" icon unicode character. + /// + Paw = 0xF1B, + + /// + /// The Font Awesome "paypal" icon unicode character. + /// + Paypal = 0xF1ED, + + /// + /// The Font Awesome "peace" icon unicode character. + /// + Peace = 0xF67C, + + /// + /// The Font Awesome "pen" icon unicode character. + /// + Pen = 0xF304, + + /// + /// The Font Awesome "pen-alt" icon unicode character. + /// + PenAlt = 0xF305, + + /// + /// The Font Awesome "pencil-alt" icon unicode character. + /// + PencilAlt = 0xF303, + + /// + /// The Font Awesome "pencil-ruler" icon unicode character. + /// + PencilRuler = 0xF5AE, + + /// + /// The Font Awesome "pen-fancy" icon unicode character. + /// + PenFancy = 0xF5AC, + + /// + /// The Font Awesome "pen-nib" icon unicode character. + /// + PenNib = 0xF5AD, + + /// + /// The Font Awesome "penny-arcade" icon unicode character. + /// + PennyArcade = 0xF704, + + /// + /// The Font Awesome "pen-square" icon unicode character. + /// + PenSquare = 0xF14B, + + /// + /// The Font Awesome "people-carry" icon unicode character. + /// + PeopleCarry = 0xF4CE, + + /// + /// The Font Awesome "pepper-hot" icon unicode character. + /// + PepperHot = 0xF816, + + /// + /// The Font Awesome "percent" icon unicode character. + /// + Percent = 0xF295, + + /// + /// The Font Awesome "percentage" icon unicode character. + /// + Percentage = 0xF541, + + /// + /// The Font Awesome "periscope" icon unicode character. + /// + Periscope = 0xF3DA, + + /// + /// The Font Awesome "person-booth" icon unicode character. + /// + PersonBooth = 0xF756, + + /// + /// The Font Awesome "phabricator" icon unicode character. + /// + Phabricator = 0xF3DB, + + /// + /// The Font Awesome "phoenix-framework" icon unicode character. + /// + PhoenixFramework = 0xF3DC, + + /// + /// The Font Awesome "phoenix-squadron" icon unicode character. + /// + PhoenixSquadron = 0xF511, + + /// + /// The Font Awesome "phone" icon unicode character. + /// + Phone = 0xF095, + + /// + /// The Font Awesome "phone-alt" icon unicode character. + /// + PhoneAlt = 0xF879, + + /// + /// The Font Awesome "phone-slash" icon unicode character. + /// + PhoneSlash = 0xF3DD, + + /// + /// The Font Awesome "phone-square" icon unicode character. + /// + PhoneSquare = 0xF098, + + /// + /// The Font Awesome "phone-square-alt" icon unicode character. + /// + PhoneSquareAlt = 0xF87B, + + /// + /// The Font Awesome "phone-volume" icon unicode character. + /// + PhoneVolume = 0xF2A, + + /// + /// The Font Awesome "photo-video" icon unicode character. + /// + PhotoVideo = 0xF87C, + + /// + /// The Font Awesome "php" icon unicode character. + /// + Php = 0xF457, + + /// + /// The Font Awesome "pied-piper" icon unicode character. + /// + PiedPiper = 0xF2AE, + + /// + /// The Font Awesome "pied-piper-alt" icon unicode character. + /// + PiedPiperAlt = 0xF1A8, + + /// + /// The Font Awesome "pied-piper-hat" icon unicode character. + /// + PiedPiperHat = 0xF4E5, + + /// + /// The Font Awesome "pied-piper-pp" icon unicode character. + /// + PiedPiperPp = 0xF1A7, + + /// + /// The Font Awesome "pied-piper-square" icon unicode character. + /// + PiedPiperSquare = 0xF91E, + + /// + /// The Font Awesome "piggy-bank" icon unicode character. + /// + PiggyBank = 0xF4D3, + + /// + /// The Font Awesome "pills" icon unicode character. + /// + Pills = 0xF484, + + /// + /// The Font Awesome "pinterest" icon unicode character. + /// + Pinterest = 0xF0D2, + + /// + /// The Font Awesome "pinterest-p" icon unicode character. + /// + PinterestP = 0xF231, + + /// + /// The Font Awesome "pinterest-square" icon unicode character. + /// + PinterestSquare = 0xF0D3, + + /// + /// The Font Awesome "pizza-slice" icon unicode character. + /// + PizzaSlice = 0xF818, + + /// + /// The Font Awesome "place-of-worship" icon unicode character. + /// + PlaceOfWorship = 0xF67F, + + /// + /// The Font Awesome "plane" icon unicode character. + /// + Plane = 0xF072, + + /// + /// The Font Awesome "plane-arrival" icon unicode character. + /// + PlaneArrival = 0xF5AF, + + /// + /// The Font Awesome "plane-departure" icon unicode character. + /// + PlaneDeparture = 0xF5B, + + /// + /// The Font Awesome "play" icon unicode character. + /// + Play = 0xF04B, + + /// + /// The Font Awesome "play-circle" icon unicode character. + /// + PlayCircle = 0xF144, + + /// + /// The Font Awesome "playstation" icon unicode character. + /// + Playstation = 0xF3DF, + + /// + /// The Font Awesome "plug" icon unicode character. + /// + Plug = 0xF1E6, + + /// + /// The Font Awesome "plus" icon unicode character. + /// + Plus = 0xF067, + + /// + /// The Font Awesome "plus-circle" icon unicode character. + /// + PlusCircle = 0xF055, + + /// + /// The Font Awesome "plus-square" icon unicode character. + /// + PlusSquare = 0xF0FE, + + /// + /// The Font Awesome "podcast" icon unicode character. + /// + Podcast = 0xF2CE, + + /// + /// The Font Awesome "poll" icon unicode character. + /// + Poll = 0xF681, + + /// + /// The Font Awesome "poll-h" icon unicode character. + /// + PollH = 0xF682, + + /// + /// The Font Awesome "poo" icon unicode character. + /// + Poo = 0xF2FE, + + /// + /// The Font Awesome "poop" icon unicode character. + /// + Poop = 0xF619, + + /// + /// The Font Awesome "poo-storm" icon unicode character. + /// + PooStorm = 0xF75A, + + /// + /// The Font Awesome "portrait" icon unicode character. + /// + Portrait = 0xF3E, + + /// + /// The Font Awesome "pound-sign" icon unicode character. + /// + PoundSign = 0xF154, + + /// + /// The Font Awesome "power-off" icon unicode character. + /// + PowerOff = 0xF011, + + /// + /// The Font Awesome "pray" icon unicode character. + /// + Pray = 0xF683, + + /// + /// The Font Awesome "praying-hands" icon unicode character. + /// + PrayingHands = 0xF684, + + /// + /// The Font Awesome "prescription" icon unicode character. + /// + Prescription = 0xF5B1, + + /// + /// The Font Awesome "prescription-bottle" icon unicode character. + /// + PrescriptionBottle = 0xF485, + + /// + /// The Font Awesome "prescription-bottle-alt" icon unicode character. + /// + PrescriptionBottleAlt = 0xF486, + + /// + /// The Font Awesome "print" icon unicode character. + /// + Print = 0xF02F, + + /// + /// The Font Awesome "procedures" icon unicode character. + /// + Procedures = 0xF487, + + /// + /// The Font Awesome "product-hunt" icon unicode character. + /// + ProductHunt = 0xF288, + + /// + /// The Font Awesome "project-diagram" icon unicode character. + /// + ProjectDiagram = 0xF542, + + /// + /// The Font Awesome "pushed" icon unicode character. + /// + Pushed = 0xF3E1, + + /// + /// The Font Awesome "puzzle-piece" icon unicode character. + /// + PuzzlePiece = 0xF12E, + + /// + /// The Font Awesome "python" icon unicode character. + /// + Python = 0xF3E2, + + /// + /// The Font Awesome "qq" icon unicode character. + /// + Qq = 0xF1D6, + + /// + /// The Font Awesome "qrcode" icon unicode character. + /// + Qrcode = 0xF029, + + /// + /// The Font Awesome "question" icon unicode character. + /// + Question = 0xF128, + + /// + /// The Font Awesome "question-circle" icon unicode character. + /// + QuestionCircle = 0xF059, + + /// + /// The Font Awesome "quidditch" icon unicode character. + /// + Quidditch = 0xF458, + + /// + /// The Font Awesome "quinscape" icon unicode character. + /// + Quinscape = 0xF459, + + /// + /// The Font Awesome "quora" icon unicode character. + /// + Quora = 0xF2C4, + + /// + /// The Font Awesome "quote-left" icon unicode character. + /// + QuoteLeft = 0xF10D, + + /// + /// The Font Awesome "quote-right" icon unicode character. + /// + QuoteRight = 0xF10E, + + /// + /// The Font Awesome "quran" icon unicode character. + /// + Quran = 0xF687, + + /// + /// The Font Awesome "radiation" icon unicode character. + /// + Radiation = 0xF7B9, + + /// + /// The Font Awesome "radiation-alt" icon unicode character. + /// + RadiationAlt = 0xF7BA, + + /// + /// The Font Awesome "rainbow" icon unicode character. + /// + Rainbow = 0xF75B, + + /// + /// The Font Awesome "random" icon unicode character. + /// + Random = 0xF074, + + /// + /// The Font Awesome "raspberry-pi" icon unicode character. + /// + RaspberryPi = 0xF7BB, + + /// + /// The Font Awesome "ravelry" icon unicode character. + /// + Ravelry = 0xF2D9, + + /// + /// The Font Awesome "react" icon unicode character. + /// + React = 0xF41B, + + /// + /// The Font Awesome "reacteurope" icon unicode character. + /// + Reacteurope = 0xF75D, + + /// + /// The Font Awesome "readme" icon unicode character. + /// + Readme = 0xF4D5, + + /// + /// The Font Awesome "rebel" icon unicode character. + /// + Rebel = 0xF1D, + + /// + /// The Font Awesome "receipt" icon unicode character. + /// + Receipt = 0xF543, + + /// + /// The Font Awesome "record-vinyl" icon unicode character. + /// + RecordVinyl = 0xF8D9, + + /// + /// The Font Awesome "recycle" icon unicode character. + /// + Recycle = 0xF1B8, + + /// + /// The Font Awesome "reddit" icon unicode character. + /// + Reddit = 0xF1A1, + + /// + /// The Font Awesome "reddit-alien" icon unicode character. + /// + RedditAlien = 0xF281, + + /// + /// The Font Awesome "reddit-square" icon unicode character. + /// + RedditSquare = 0xF1A2, + + /// + /// The Font Awesome "redhat" icon unicode character. + /// + Redhat = 0xF7BC, + + /// + /// The Font Awesome "redo" icon unicode character. + /// + Redo = 0xF01E, + + /// + /// The Font Awesome "redo-alt" icon unicode character. + /// + RedoAlt = 0xF2F9, + + /// + /// The Font Awesome "red-river" icon unicode character. + /// + RedRiver = 0xF3E3, + + /// + /// The Font Awesome "registered" icon unicode character. + /// + Registered = 0xF25D, + + /// + /// The Font Awesome "remove-format" icon unicode character. + /// + RemoveFormat = 0xF87D, + + /// + /// The Font Awesome "renren" icon unicode character. + /// + Renren = 0xF18B, + + /// + /// The Font Awesome "reply" icon unicode character. + /// + Reply = 0xF3E5, + + /// + /// The Font Awesome "reply-all" icon unicode character. + /// + ReplyAll = 0xF122, + + /// + /// The Font Awesome "replyd" icon unicode character. + /// + Replyd = 0xF3E6, + + /// + /// The Font Awesome "republican" icon unicode character. + /// + Republican = 0xF75E, + + /// + /// The Font Awesome "researchgate" icon unicode character. + /// + Researchgate = 0xF4F8, + + /// + /// The Font Awesome "resolving" icon unicode character. + /// + Resolving = 0xF3E7, + + /// + /// The Font Awesome "restroom" icon unicode character. + /// + Restroom = 0xF7BD, + + /// + /// The Font Awesome "retweet" icon unicode character. + /// + Retweet = 0xF079, + + /// + /// The Font Awesome "rev" icon unicode character. + /// + Rev = 0xF5B2, + + /// + /// The Font Awesome "ribbon" icon unicode character. + /// + Ribbon = 0xF4D6, + + /// + /// The Font Awesome "ring" icon unicode character. + /// + Ring = 0xF70B, + + /// + /// The Font Awesome "road" icon unicode character. + /// + Road = 0xF018, + + /// + /// The Font Awesome "robot" icon unicode character. + /// + Robot = 0xF544, + + /// + /// The Font Awesome "rocket" icon unicode character. + /// + Rocket = 0xF135, + + /// + /// The Font Awesome "rocketchat" icon unicode character. + /// + Rocketchat = 0xF3E8, + + /// + /// The Font Awesome "rockrms" icon unicode character. + /// + Rockrms = 0xF3E9, + + /// + /// The Font Awesome "route" icon unicode character. + /// + Route = 0xF4D7, + + /// + /// The Font Awesome "r-project" icon unicode character. + /// + RProject = 0xF4F7, + + /// + /// The Font Awesome "rss" icon unicode character. + /// + Rss = 0xF09E, + + /// + /// The Font Awesome "rss-square" icon unicode character. + /// + RssSquare = 0xF143, + + /// + /// The Font Awesome "ruble-sign" icon unicode character. + /// + RubleSign = 0xF158, + + /// + /// The Font Awesome "ruler" icon unicode character. + /// + Ruler = 0xF545, + + /// + /// The Font Awesome "ruler-combined" icon unicode character. + /// + RulerCombined = 0xF546, + + /// + /// The Font Awesome "ruler-horizontal" icon unicode character. + /// + RulerHorizontal = 0xF547, + + /// + /// The Font Awesome "ruler-vertical" icon unicode character. + /// + RulerVertical = 0xF548, + + /// + /// The Font Awesome "running" icon unicode character. + /// + Running = 0xF70C, + + /// + /// The Font Awesome "rupee-sign" icon unicode character. + /// + RupeeSign = 0xF156, + + /// + /// The Font Awesome "sad-cry" icon unicode character. + /// + SadCry = 0xF5B3, + + /// + /// The Font Awesome "sad-tear" icon unicode character. + /// + SadTear = 0xF5B4, + + /// + /// The Font Awesome "safari" icon unicode character. + /// + Safari = 0xF267, + + /// + /// The Font Awesome "salesforce" icon unicode character. + /// + Salesforce = 0xF83B, + + /// + /// The Font Awesome "sass" icon unicode character. + /// + Sass = 0xF41E, + + /// + /// The Font Awesome "satellite" icon unicode character. + /// + Satellite = 0xF7BF, + + /// + /// The Font Awesome "satellite-dish" icon unicode character. + /// + SatelliteDish = 0xF7C, + + /// + /// The Font Awesome "save" icon unicode character. + /// + Save = 0xF0C7, + + /// + /// The Font Awesome "schlix" icon unicode character. + /// + Schlix = 0xF3EA, + + /// + /// The Font Awesome "school" icon unicode character. + /// + School = 0xF549, + + /// + /// The Font Awesome "screwdriver" icon unicode character. + /// + Screwdriver = 0xF54A, + + /// + /// The Font Awesome "scribd" icon unicode character. + /// + Scribd = 0xF28A, + + /// + /// The Font Awesome "scroll" icon unicode character. + /// + Scroll = 0xF70E, + + /// + /// The Font Awesome "sd-card" icon unicode character. + /// + SdCard = 0xF7C2, + + /// + /// The Font Awesome "search" icon unicode character. + /// + Search = 0xF002, + + /// + /// The Font Awesome "search-dollar" icon unicode character. + /// + SearchDollar = 0xF688, + + /// + /// The Font Awesome "searchengin" icon unicode character. + /// + Searchengin = 0xF3EB, + + /// + /// The Font Awesome "search-location" icon unicode character. + /// + SearchLocation = 0xF689, + + /// + /// The Font Awesome "search-minus" icon unicode character. + /// + SearchMinus = 0xF01, + + /// + /// The Font Awesome "search-plus" icon unicode character. + /// + SearchPlus = 0xF00E, + + /// + /// The Font Awesome "seedling" icon unicode character. + /// + Seedling = 0xF4D8, + + /// + /// The Font Awesome "sellcast" icon unicode character. + /// + Sellcast = 0xF2DA, + + /// + /// The Font Awesome "sellsy" icon unicode character. + /// + Sellsy = 0xF213, + + /// + /// The Font Awesome "server" icon unicode character. + /// + Server = 0xF233, + + /// + /// The Font Awesome "servicestack" icon unicode character. + /// + Servicestack = 0xF3EC, + + /// + /// The Font Awesome "shapes" icon unicode character. + /// + Shapes = 0xF61F, + + /// + /// The Font Awesome "share" icon unicode character. + /// + Share = 0xF064, + + /// + /// The Font Awesome "share-alt" icon unicode character. + /// + ShareAlt = 0xF1E, + + /// + /// The Font Awesome "share-alt-square" icon unicode character. + /// + ShareAltSquare = 0xF1E1, + + /// + /// The Font Awesome "share-square" icon unicode character. + /// + ShareSquare = 0xF14D, + + /// + /// The Font Awesome "shekel-sign" icon unicode character. + /// + ShekelSign = 0xF20B, + + /// + /// The Font Awesome "shield-alt" icon unicode character. + /// + ShieldAlt = 0xF3ED, + + /// + /// The Font Awesome "ship" icon unicode character. + /// + Ship = 0xF21A, + + /// + /// The Font Awesome "shipping-fast" icon unicode character. + /// + ShippingFast = 0xF48B, + + /// + /// The Font Awesome "shirtsinbulk" icon unicode character. + /// + Shirtsinbulk = 0xF214, + + /// + /// The Font Awesome "shoe-prints" icon unicode character. + /// + ShoePrints = 0xF54B, + + /// + /// The Font Awesome "shopify" icon unicode character. + /// + Shopify = 0xF957, + + /// + /// The Font Awesome "shopping-bag" icon unicode character. + /// + ShoppingBag = 0xF29, + + /// + /// The Font Awesome "shopping-basket" icon unicode character. + /// + ShoppingBasket = 0xF291, + + /// + /// The Font Awesome "shopping-cart" icon unicode character. + /// + ShoppingCart = 0xF07A, + + /// + /// The Font Awesome "shopware" icon unicode character. + /// + Shopware = 0xF5B5, + + /// + /// The Font Awesome "shower" icon unicode character. + /// + Shower = 0xF2CC, + + /// + /// The Font Awesome "shuttle-van" icon unicode character. + /// + ShuttleVan = 0xF5B6, + + /// + /// The Font Awesome "sign" icon unicode character. + /// + Sign = 0xF4D9, + + /// + /// The Font Awesome "signal" icon unicode character. + /// + Signal = 0xF012, + + /// + /// The Font Awesome "signature" icon unicode character. + /// + Signature = 0xF5B7, + + /// + /// The Font Awesome "sign-in-alt" icon unicode character. + /// + SignInAlt = 0xF2F6, + + /// + /// The Font Awesome "sign-language" icon unicode character. + /// + SignLanguage = 0xF2A7, + + /// + /// The Font Awesome "sign-out-alt" icon unicode character. + /// + SignOutAlt = 0xF2F5, + + /// + /// The Font Awesome "sim-card" icon unicode character. + /// + SimCard = 0xF7C4, + + /// + /// The Font Awesome "simplybuilt" icon unicode character. + /// + Simplybuilt = 0xF215, + + /// + /// The Font Awesome "sistrix" icon unicode character. + /// + Sistrix = 0xF3EE, + + /// + /// The Font Awesome "sitemap" icon unicode character. + /// + Sitemap = 0xF0E8, + + /// + /// The Font Awesome "sith" icon unicode character. + /// + Sith = 0xF512, + + /// + /// The Font Awesome "skating" icon unicode character. + /// + Skating = 0xF7C5, + + /// + /// The Font Awesome "sketch" icon unicode character. + /// + Sketch = 0xF7C6, + + /// + /// The Font Awesome "skiing" icon unicode character. + /// + Skiing = 0xF7C9, + + /// + /// The Font Awesome "skiing-nordic" icon unicode character. + /// + SkiingNordic = 0xF7CA, + + /// + /// The Font Awesome "skull" icon unicode character. + /// + Skull = 0xF54C, + + /// + /// The Font Awesome "skull-crossbones" icon unicode character. + /// + SkullCrossbones = 0xF714, + + /// + /// The Font Awesome "skyatlas" icon unicode character. + /// + Skyatlas = 0xF216, + + /// + /// The Font Awesome "skype" icon unicode character. + /// + Skype = 0xF17E, + + /// + /// The Font Awesome "slack" icon unicode character. + /// + Slack = 0xF198, + + /// + /// The Font Awesome "slack-hash" icon unicode character. + /// + SlackHash = 0xF3EF, + + /// + /// The Font Awesome "slash" icon unicode character. + /// + Slash = 0xF715, + + /// + /// The Font Awesome "sleigh" icon unicode character. + /// + Sleigh = 0xF7CC, + + /// + /// The Font Awesome "sliders-h" icon unicode character. + /// + SlidersH = 0xF1DE, + + /// + /// The Font Awesome "slideshare" icon unicode character. + /// + Slideshare = 0xF1E7, + + /// + /// The Font Awesome "smile" icon unicode character. + /// + Smile = 0xF118, + + /// + /// The Font Awesome "smile-beam" icon unicode character. + /// + SmileBeam = 0xF5B8, + + /// + /// The Font Awesome "smile-wink" icon unicode character. + /// + SmileWink = 0xF4DA, + + /// + /// The Font Awesome "smog" icon unicode character. + /// + Smog = 0xF75F, + + /// + /// The Font Awesome "smoking" icon unicode character. + /// + Smoking = 0xF48D, + + /// + /// The Font Awesome "smoking-ban" icon unicode character. + /// + SmokingBan = 0xF54D, + + /// + /// The Font Awesome "sms" icon unicode character. + /// + Sms = 0xF7CD, + + /// + /// The Font Awesome "snapchat" icon unicode character. + /// + Snapchat = 0xF2AB, + + /// + /// The Font Awesome "snapchat-ghost" icon unicode character. + /// + SnapchatGhost = 0xF2AC, + + /// + /// The Font Awesome "snapchat-square" icon unicode character. + /// + SnapchatSquare = 0xF2AD, + + /// + /// The Font Awesome "snowboarding" icon unicode character. + /// + Snowboarding = 0xF7CE, + + /// + /// The Font Awesome "snowflake" icon unicode character. + /// + Snowflake = 0xF2DC, + + /// + /// The Font Awesome "snowman" icon unicode character. + /// + Snowman = 0xF7D, + + /// + /// The Font Awesome "snowplow" icon unicode character. + /// + Snowplow = 0xF7D2, + + /// + /// The Font Awesome "socks" icon unicode character. + /// + Socks = 0xF696, + + /// + /// The Font Awesome "solar-panel" icon unicode character. + /// + SolarPanel = 0xF5BA, + + /// + /// The Font Awesome "sort" icon unicode character. + /// + Sort = 0xF0DC, + + /// + /// The Font Awesome "sort-alpha-down" icon unicode character. + /// + SortAlphaDown = 0xF15D, + + /// + /// The Font Awesome "sort-alpha-down-alt" icon unicode character. + /// + SortAlphaDownAlt = 0xF881, + + /// + /// The Font Awesome "sort-alpha-up" icon unicode character. + /// + SortAlphaUp = 0xF15E, + + /// + /// The Font Awesome "sort-alpha-up-alt" icon unicode character. + /// + SortAlphaUpAlt = 0xF882, + + /// + /// The Font Awesome "sort-amount-down" icon unicode character. + /// + SortAmountDown = 0xF16, + + /// + /// The Font Awesome "sort-amount-down-alt" icon unicode character. + /// + SortAmountDownAlt = 0xF884, + + /// + /// The Font Awesome "sort-amount-up" icon unicode character. + /// + SortAmountUp = 0xF161, + + /// + /// The Font Awesome "sort-amount-up-alt" icon unicode character. + /// + SortAmountUpAlt = 0xF885, + + /// + /// The Font Awesome "sort-down" icon unicode character. + /// + SortDown = 0xF0DD, + + /// + /// The Font Awesome "sort-numeric-down" icon unicode character. + /// + SortNumericDown = 0xF162, + + /// + /// The Font Awesome "sort-numeric-down-alt" icon unicode character. + /// + SortNumericDownAlt = 0xF886, + + /// + /// The Font Awesome "sort-numeric-up" icon unicode character. + /// + SortNumericUp = 0xF163, + + /// + /// The Font Awesome "sort-numeric-up-alt" icon unicode character. + /// + SortNumericUpAlt = 0xF887, + + /// + /// The Font Awesome "sort-up" icon unicode character. + /// + SortUp = 0xF0DE, + + /// + /// The Font Awesome "soundcloud" icon unicode character. + /// + Soundcloud = 0xF1BE, + + /// + /// The Font Awesome "sourcetree" icon unicode character. + /// + Sourcetree = 0xF7D3, + + /// + /// The Font Awesome "spa" icon unicode character. + /// + Spa = 0xF5BB, + + /// + /// The Font Awesome "space-shuttle" icon unicode character. + /// + SpaceShuttle = 0xF197, + + /// + /// The Font Awesome "speakap" icon unicode character. + /// + Speakap = 0xF3F3, + + /// + /// The Font Awesome "speaker-deck" icon unicode character. + /// + SpeakerDeck = 0xF83C, + + /// + /// The Font Awesome "spell-check" icon unicode character. + /// + SpellCheck = 0xF891, + + /// + /// The Font Awesome "spider" icon unicode character. + /// + Spider = 0xF717, + + /// + /// The Font Awesome "spinner" icon unicode character. + /// + Spinner = 0xF11, + + /// + /// The Font Awesome "splotch" icon unicode character. + /// + Splotch = 0xF5BC, + + /// + /// The Font Awesome "spotify" icon unicode character. + /// + Spotify = 0xF1BC, + + /// + /// The Font Awesome "spray-can" icon unicode character. + /// + SprayCan = 0xF5BD, + + /// + /// The Font Awesome "square" icon unicode character. + /// + Square = 0xF0C8, + + /// + /// The Font Awesome "square-full" icon unicode character. + /// + SquareFull = 0xF45C, + + /// + /// The Font Awesome "square-root-alt" icon unicode character. + /// + SquareRootAlt = 0xF698, + + /// + /// The Font Awesome "squarespace" icon unicode character. + /// + Squarespace = 0xF5BE, + + /// + /// The Font Awesome "stack-exchange" icon unicode character. + /// + StackExchange = 0xF18D, + + /// + /// The Font Awesome "stack-overflow" icon unicode character. + /// + StackOverflow = 0xF16C, + + /// + /// The Font Awesome "stackpath" icon unicode character. + /// + Stackpath = 0xF842, + + /// + /// The Font Awesome "stamp" icon unicode character. + /// + Stamp = 0xF5BF, + + /// + /// The Font Awesome "star" icon unicode character. + /// + Star = 0xF005, + + /// + /// The Font Awesome "star-and-crescent" icon unicode character. + /// + StarAndCrescent = 0xF699, + + /// + /// The Font Awesome "star-half" icon unicode character. + /// + StarHalf = 0xF089, + + /// + /// The Font Awesome "star-half-alt" icon unicode character. + /// + StarHalfAlt = 0xF5C, + + /// + /// The Font Awesome "star-of-david" icon unicode character. + /// + StarOfDavid = 0xF69A, + + /// + /// The Font Awesome "star-of-life" icon unicode character. + /// + StarOfLife = 0xF621, + + /// + /// The Font Awesome "staylinked" icon unicode character. + /// + Staylinked = 0xF3F5, + + /// + /// The Font Awesome "steam" icon unicode character. + /// + Steam = 0xF1B6, + + /// + /// The Font Awesome "steam-square" icon unicode character. + /// + SteamSquare = 0xF1B7, + + /// + /// The Font Awesome "steam-symbol" icon unicode character. + /// + SteamSymbol = 0xF3F6, + + /// + /// The Font Awesome "step-backward" icon unicode character. + /// + StepBackward = 0xF048, + + /// + /// The Font Awesome "step-forward" icon unicode character. + /// + StepForward = 0xF051, + + /// + /// The Font Awesome "stethoscope" icon unicode character. + /// + Stethoscope = 0xF0F1, + + /// + /// The Font Awesome "sticker-mule" icon unicode character. + /// + StickerMule = 0xF3F7, + + /// + /// The Font Awesome "sticky-note" icon unicode character. + /// + StickyNote = 0xF249, + + /// + /// The Font Awesome "stop" icon unicode character. + /// + Stop = 0xF04D, + + /// + /// The Font Awesome "stop-circle" icon unicode character. + /// + StopCircle = 0xF28D, + + /// + /// The Font Awesome "stopwatch" icon unicode character. + /// + Stopwatch = 0xF2F2, + + /// + /// The Font Awesome "store" icon unicode character. + /// + Store = 0xF54E, + + /// + /// The Font Awesome "store-alt" icon unicode character. + /// + StoreAlt = 0xF54F, + + /// + /// The Font Awesome "strava" icon unicode character. + /// + Strava = 0xF428, + + /// + /// The Font Awesome "stream" icon unicode character. + /// + Stream = 0xF55, + + /// + /// The Font Awesome "street-view" icon unicode character. + /// + StreetView = 0xF21D, + + /// + /// The Font Awesome "strikethrough" icon unicode character. + /// + Strikethrough = 0xF0CC, + + /// + /// The Font Awesome "stripe" icon unicode character. + /// + Stripe = 0xF429, + + /// + /// The Font Awesome "stripe-s" icon unicode character. + /// + StripeS = 0xF42A, + + /// + /// The Font Awesome "stroopwafel" icon unicode character. + /// + Stroopwafel = 0xF551, + + /// + /// The Font Awesome "studiovinari" icon unicode character. + /// + Studiovinari = 0xF3F8, + + /// + /// The Font Awesome "stumbleupon" icon unicode character. + /// + Stumbleupon = 0xF1A4, + + /// + /// The Font Awesome "stumbleupon-circle" icon unicode character. + /// + StumbleuponCircle = 0xF1A3, + + /// + /// The Font Awesome "subscript" icon unicode character. + /// + Subscript = 0xF12C, + + /// + /// The Font Awesome "subway" icon unicode character. + /// + Subway = 0xF239, + + /// + /// The Font Awesome "suitcase" icon unicode character. + /// + Suitcase = 0xF0F2, + + /// + /// The Font Awesome "suitcase-rolling" icon unicode character. + /// + SuitcaseRolling = 0xF5C1, + + /// + /// The Font Awesome "sun" icon unicode character. + /// + Sun = 0xF185, + + /// + /// The Font Awesome "superpowers" icon unicode character. + /// + Superpowers = 0xF2DD, + + /// + /// The Font Awesome "superscript" icon unicode character. + /// + Superscript = 0xF12B, + + /// + /// The Font Awesome "supple" icon unicode character. + /// + Supple = 0xF3F9, + + /// + /// The Font Awesome "surprise" icon unicode character. + /// + Surprise = 0xF5C2, + + /// + /// The Font Awesome "suse" icon unicode character. + /// + Suse = 0xF7D6, + + /// + /// The Font Awesome "swatchbook" icon unicode character. + /// + Swatchbook = 0xF5C3, + + /// + /// The Font Awesome "swift" icon unicode character. + /// + Swift = 0xF8E1, + + /// + /// The Font Awesome "swimmer" icon unicode character. + /// + Swimmer = 0xF5C4, + + /// + /// The Font Awesome "swimming-pool" icon unicode character. + /// + SwimmingPool = 0xF5C5, + + /// + /// The Font Awesome "symfony" icon unicode character. + /// + Symfony = 0xF83D, + + /// + /// The Font Awesome "synagogue" icon unicode character. + /// + Synagogue = 0xF69B, + + /// + /// The Font Awesome "sync" icon unicode character. + /// + Sync = 0xF021, + + /// + /// The Font Awesome "sync-alt" icon unicode character. + /// + SyncAlt = 0xF2F1, + + /// + /// The Font Awesome "syringe" icon unicode character. + /// + Syringe = 0xF48E, + + /// + /// The Font Awesome "table" icon unicode character. + /// + Table = 0xF0CE, + + /// + /// The Font Awesome "tablet" icon unicode character. + /// + Tablet = 0xF10A, + + /// + /// The Font Awesome "tablet-alt" icon unicode character. + /// + TabletAlt = 0xF3FA, + + /// + /// The Font Awesome "table-tennis" icon unicode character. + /// + TableTennis = 0xF45D, + + /// + /// The Font Awesome "tablets" icon unicode character. + /// + Tablets = 0xF49, + + /// + /// The Font Awesome "tachometer-alt" icon unicode character. + /// + TachometerAlt = 0xF3FD, + + /// + /// The Font Awesome "tag" icon unicode character. + /// + Tag = 0xF02B, + + /// + /// The Font Awesome "tags" icon unicode character. + /// + Tags = 0xF02C, + + /// + /// The Font Awesome "tape" icon unicode character. + /// + Tape = 0xF4DB, + + /// + /// The Font Awesome "tasks" icon unicode character. + /// + Tasks = 0xF0AE, + + /// + /// The Font Awesome "taxi" icon unicode character. + /// + Taxi = 0xF1BA, + + /// + /// The Font Awesome "teamspeak" icon unicode character. + /// + Teamspeak = 0xF4F9, + + /// + /// The Font Awesome "teeth" icon unicode character. + /// + Teeth = 0xF62E, + + /// + /// The Font Awesome "teeth-open" icon unicode character. + /// + TeethOpen = 0xF62F, + + /// + /// The Font Awesome "telegram" icon unicode character. + /// + Telegram = 0xF2C6, + + /// + /// The Font Awesome "telegram-plane" icon unicode character. + /// + TelegramPlane = 0xF3FE, + + /// + /// The Font Awesome "temperature-high" icon unicode character. + /// + TemperatureHigh = 0xF769, + + /// + /// The Font Awesome "temperature-low" icon unicode character. + /// + TemperatureLow = 0xF76B, + + /// + /// The Font Awesome "tencent-weibo" icon unicode character. + /// + TencentWeibo = 0xF1D5, + + /// + /// The Font Awesome "tenge" icon unicode character. + /// + Tenge = 0xF7D7, + + /// + /// The Font Awesome "terminal" icon unicode character. + /// + Terminal = 0xF12, + + /// + /// The Font Awesome "text-height" icon unicode character. + /// + TextHeight = 0xF034, + + /// + /// The Font Awesome "text-width" icon unicode character. + /// + TextWidth = 0xF035, + + /// + /// The Font Awesome "th" icon unicode character. + /// + Th = 0xF00A, + + /// + /// The Font Awesome "theater-masks" icon unicode character. + /// + TheaterMasks = 0xF63, + + /// + /// The Font Awesome "themeco" icon unicode character. + /// + Themeco = 0xF5C6, + + /// + /// The Font Awesome "themeisle" icon unicode character. + /// + Themeisle = 0xF2B2, + + /// + /// The Font Awesome "the-red-yeti" icon unicode character. + /// + TheRedYeti = 0xF69D, + + /// + /// The Font Awesome "thermometer" icon unicode character. + /// + Thermometer = 0xF491, + + /// + /// The Font Awesome "thermometer-empty" icon unicode character. + /// + ThermometerEmpty = 0xF2CB, + + /// + /// The Font Awesome "thermometer-full" icon unicode character. + /// + ThermometerFull = 0xF2C7, + + /// + /// The Font Awesome "thermometer-half" icon unicode character. + /// + ThermometerHalf = 0xF2C9, + + /// + /// The Font Awesome "thermometer-quarter" icon unicode character. + /// + ThermometerQuarter = 0xF2CA, + + /// + /// The Font Awesome "thermometer-three-quarters" icon unicode character. + /// + ThermometerThreeQuarters = 0xF2C8, + + /// + /// The Font Awesome "think-peaks" icon unicode character. + /// + ThinkPeaks = 0xF731, + + /// + /// The Font Awesome "th-large" icon unicode character. + /// + ThLarge = 0xF009, + + /// + /// The Font Awesome "th-list" icon unicode character. + /// + ThList = 0xF00B, + + /// + /// The Font Awesome "thumbs-down" icon unicode character. + /// + ThumbsDown = 0xF165, + + /// + /// The Font Awesome "thumbs-up" icon unicode character. + /// + ThumbsUp = 0xF164, + + /// + /// The Font Awesome "thumbtack" icon unicode character. + /// + Thumbtack = 0xF08D, + + /// + /// The Font Awesome "ticket-alt" icon unicode character. + /// + TicketAlt = 0xF3FF, + + /// + /// The Font Awesome "times" icon unicode character. + /// + Times = 0xF00D, + + /// + /// The Font Awesome "times-circle" icon unicode character. + /// + TimesCircle = 0xF057, + + /// + /// The Font Awesome "tint" icon unicode character. + /// + Tint = 0xF043, + + /// + /// The Font Awesome "tint-slash" icon unicode character. + /// + TintSlash = 0xF5C7, + + /// + /// The Font Awesome "tired" icon unicode character. + /// + Tired = 0xF5C8, + + /// + /// The Font Awesome "toggle-off" icon unicode character. + /// + ToggleOff = 0xF204, + + /// + /// The Font Awesome "toggle-on" icon unicode character. + /// + ToggleOn = 0xF205, + + /// + /// The Font Awesome "toilet" icon unicode character. + /// + Toilet = 0xF7D8, + + /// + /// The Font Awesome "toilet-paper" icon unicode character. + /// + ToiletPaper = 0xF71E, + + /// + /// The Font Awesome "toolbox" icon unicode character. + /// + Toolbox = 0xF552, + + /// + /// The Font Awesome "tools" icon unicode character. + /// + Tools = 0xF7D9, + + /// + /// The Font Awesome "tooth" icon unicode character. + /// + Tooth = 0xF5C9, + + /// + /// The Font Awesome "torah" icon unicode character. + /// + Torah = 0xF6A, + + /// + /// The Font Awesome "torii-gate" icon unicode character. + /// + ToriiGate = 0xF6A1, + + /// + /// The Font Awesome "tractor" icon unicode character. + /// + Tractor = 0xF722, + + /// + /// The Font Awesome "trade-federation" icon unicode character. + /// + TradeFederation = 0xF513, + + /// + /// The Font Awesome "trademark" icon unicode character. + /// + Trademark = 0xF25C, + + /// + /// The Font Awesome "traffic-light" icon unicode character. + /// + TrafficLight = 0xF637, + + /// + /// The Font Awesome "trailer" icon unicode character. + /// + Trailer = 0xF941, + + /// + /// The Font Awesome "train" icon unicode character. + /// + Train = 0xF238, + + /// + /// The Font Awesome "tram" icon unicode character. + /// + Tram = 0xF7DA, + + /// + /// The Font Awesome "transgender" icon unicode character. + /// + Transgender = 0xF224, + + /// + /// The Font Awesome "transgender-alt" icon unicode character. + /// + TransgenderAlt = 0xF225, + + /// + /// The Font Awesome "trash" icon unicode character. + /// + Trash = 0xF1F8, + + /// + /// The Font Awesome "trash-alt" icon unicode character. + /// + TrashAlt = 0xF2ED, + + /// + /// The Font Awesome "trash-restore" icon unicode character. + /// + TrashRestore = 0xF829, + + /// + /// The Font Awesome "trash-restore-alt" icon unicode character. + /// + TrashRestoreAlt = 0xF82A, + + /// + /// The Font Awesome "tree" icon unicode character. + /// + Tree = 0xF1BB, + + /// + /// The Font Awesome "trello" icon unicode character. + /// + Trello = 0xF181, + + /// + /// The Font Awesome "tripadvisor" icon unicode character. + /// + Tripadvisor = 0xF262, + + /// + /// The Font Awesome "trophy" icon unicode character. + /// + Trophy = 0xF091, + + /// + /// The Font Awesome "truck" icon unicode character. + /// + Truck = 0xF0D1, + + /// + /// The Font Awesome "truck-loading" icon unicode character. + /// + TruckLoading = 0xF4DE, + + /// + /// The Font Awesome "truck-monster" icon unicode character. + /// + TruckMonster = 0xF63B, + + /// + /// The Font Awesome "truck-moving" icon unicode character. + /// + TruckMoving = 0xF4DF, + + /// + /// The Font Awesome "truck-pickup" icon unicode character. + /// + TruckPickup = 0xF63C, + + /// + /// The Font Awesome "tshirt" icon unicode character. + /// + Tshirt = 0xF553, + + /// + /// The Font Awesome "tty" icon unicode character. + /// + Tty = 0xF1E4, + + /// + /// The Font Awesome "tumblr" icon unicode character. + /// + Tumblr = 0xF173, + + /// + /// The Font Awesome "tumblr-square" icon unicode character. + /// + TumblrSquare = 0xF174, + + /// + /// The Font Awesome "tv" icon unicode character. + /// + Tv = 0xF26C, + + /// + /// The Font Awesome "twitch" icon unicode character. + /// + Twitch = 0xF1E8, + + /// + /// The Font Awesome "twitter" icon unicode character. + /// + Twitter = 0xF099, + + /// + /// The Font Awesome "twitter-square" icon unicode character. + /// + TwitterSquare = 0xF081, + + /// + /// The Font Awesome "typo3" icon unicode character. + /// + Typo3 = 0xF42B, + + /// + /// The Font Awesome "uber" icon unicode character. + /// + Uber = 0xF402, + + /// + /// The Font Awesome "ubuntu" icon unicode character. + /// + Ubuntu = 0xF7DF, + + /// + /// The Font Awesome "uikit" icon unicode character. + /// + Uikit = 0xF403, + + /// + /// The Font Awesome "umbraco" icon unicode character. + /// + Umbraco = 0xF8E8, + + /// + /// The Font Awesome "umbrella" icon unicode character. + /// + Umbrella = 0xF0E9, + + /// + /// The Font Awesome "umbrella-beach" icon unicode character. + /// + UmbrellaBeach = 0xF5CA, + + /// + /// The Font Awesome "underline" icon unicode character. + /// + Underline = 0xF0CD, + + /// + /// The Font Awesome "undo" icon unicode character. + /// + Undo = 0xF0E2, + + /// + /// The Font Awesome "undo-alt" icon unicode character. + /// + UndoAlt = 0xF2EA, + + /// + /// The Font Awesome "uniregistry" icon unicode character. + /// + Uniregistry = 0xF404, + + /// + /// The Font Awesome "unity" icon unicode character. + /// + Unity = 0xF949, + + /// + /// The Font Awesome "universal-access" icon unicode character. + /// + UniversalAccess = 0xF29A, + + /// + /// The Font Awesome "university" icon unicode character. + /// + University = 0xF19C, + + /// + /// The Font Awesome "unlink" icon unicode character. + /// + Unlink = 0xF127, + + /// + /// The Font Awesome "unlock" icon unicode character. + /// + Unlock = 0xF09C, + + /// + /// The Font Awesome "unlock-alt" icon unicode character. + /// + UnlockAlt = 0xF13E, + + /// + /// The Font Awesome "untappd" icon unicode character. + /// + Untappd = 0xF405, + + /// + /// The Font Awesome "upload" icon unicode character. + /// + Upload = 0xF093, + + /// + /// The Font Awesome "ups" icon unicode character. + /// + Ups = 0xF7E, + + /// + /// The Font Awesome "usb" icon unicode character. + /// + Usb = 0xF287, + + /// + /// The Font Awesome "user" icon unicode character. + /// + User = 0xF007, + + /// + /// The Font Awesome "user-alt" icon unicode character. + /// + UserAlt = 0xF406, + + /// + /// The Font Awesome "user-alt-slash" icon unicode character. + /// + UserAltSlash = 0xF4FA, + + /// + /// The Font Awesome "user-astronaut" icon unicode character. + /// + UserAstronaut = 0xF4FB, + + /// + /// The Font Awesome "user-check" icon unicode character. + /// + UserCheck = 0xF4FC, + + /// + /// The Font Awesome "user-circle" icon unicode character. + /// + UserCircle = 0xF2BD, + + /// + /// The Font Awesome "user-clock" icon unicode character. + /// + UserClock = 0xF4FD, + + /// + /// The Font Awesome "user-cog" icon unicode character. + /// + UserCog = 0xF4FE, + + /// + /// The Font Awesome "user-edit" icon unicode character. + /// + UserEdit = 0xF4FF, + + /// + /// The Font Awesome "user-friends" icon unicode character. + /// + UserFriends = 0xF5, + + /// + /// The Font Awesome "user-graduate" icon unicode character. + /// + UserGraduate = 0xF501, + + /// + /// The Font Awesome "user-injured" icon unicode character. + /// + UserInjured = 0xF728, + + /// + /// The Font Awesome "user-lock" icon unicode character. + /// + UserLock = 0xF502, + + /// + /// The Font Awesome "user-md" icon unicode character. + /// + UserMd = 0xF0F, + + /// + /// The Font Awesome "user-minus" icon unicode character. + /// + UserMinus = 0xF503, + + /// + /// The Font Awesome "user-ninja" icon unicode character. + /// + UserNinja = 0xF504, + + /// + /// The Font Awesome "user-nurse" icon unicode character. + /// + UserNurse = 0xF82F, + + /// + /// The Font Awesome "user-plus" icon unicode character. + /// + UserPlus = 0xF234, + + /// + /// The Font Awesome "users" icon unicode character. + /// + Users = 0xF0C, + + /// + /// The Font Awesome "users-cog" icon unicode character. + /// + UsersCog = 0xF509, + + /// + /// The Font Awesome "user-secret" icon unicode character. + /// + UserSecret = 0xF21B, + + /// + /// The Font Awesome "user-shield" icon unicode character. + /// + UserShield = 0xF505, + + /// + /// The Font Awesome "user-slash" icon unicode character. + /// + UserSlash = 0xF506, + + /// + /// The Font Awesome "user-tag" icon unicode character. + /// + UserTag = 0xF507, + + /// + /// The Font Awesome "user-tie" icon unicode character. + /// + UserTie = 0xF508, + + /// + /// The Font Awesome "user-times" icon unicode character. + /// + UserTimes = 0xF235, + + /// + /// The Font Awesome "usps" icon unicode character. + /// + Usps = 0xF7E1, + + /// + /// The Font Awesome "ussunnah" icon unicode character. + /// + Ussunnah = 0xF407, + + /// + /// The Font Awesome "utensils" icon unicode character. + /// + Utensils = 0xF2E7, + + /// + /// The Font Awesome "utensil-spoon" icon unicode character. + /// + UtensilSpoon = 0xF2E5, + + /// + /// The Font Awesome "vaadin" icon unicode character. + /// + Vaadin = 0xF408, + + /// + /// The Font Awesome "vector-square" icon unicode character. + /// + VectorSquare = 0xF5CB, + + /// + /// The Font Awesome "venus" icon unicode character. + /// + Venus = 0xF221, + + /// + /// The Font Awesome "venus-double" icon unicode character. + /// + VenusDouble = 0xF226, + + /// + /// The Font Awesome "venus-mars" icon unicode character. + /// + VenusMars = 0xF228, + + /// + /// The Font Awesome "viacoin" icon unicode character. + /// + Viacoin = 0xF237, + + /// + /// The Font Awesome "viadeo" icon unicode character. + /// + Viadeo = 0xF2A9, + + /// + /// The Font Awesome "viadeo-square" icon unicode character. + /// + ViadeoSquare = 0xF2AA, + + /// + /// The Font Awesome "vial" icon unicode character. + /// + Vial = 0xF492, + + /// + /// The Font Awesome "vials" icon unicode character. + /// + Vials = 0xF493, + + /// + /// The Font Awesome "viber" icon unicode character. + /// + Viber = 0xF409, + + /// + /// The Font Awesome "video" icon unicode character. + /// + Video = 0xF03D, + + /// + /// The Font Awesome "video-slash" icon unicode character. + /// + VideoSlash = 0xF4E2, + + /// + /// The Font Awesome "vihara" icon unicode character. + /// + Vihara = 0xF6A7, + + /// + /// The Font Awesome "vimeo" icon unicode character. + /// + Vimeo = 0xF40A, + + /// + /// The Font Awesome "vimeo-square" icon unicode character. + /// + VimeoSquare = 0xF194, + + /// + /// The Font Awesome "vimeo-v" icon unicode character. + /// + VimeoV = 0xF27D, + + /// + /// The Font Awesome "vine" icon unicode character. + /// + Vine = 0xF1CA, + + /// + /// The Font Awesome "vk" icon unicode character. + /// + Vk = 0xF189, + + /// + /// The Font Awesome "vnv" icon unicode character. + /// + Vnv = 0xF40B, + + /// + /// The Font Awesome "voicemail" icon unicode character. + /// + Voicemail = 0xF897, + + /// + /// The Font Awesome "volleyball-ball" icon unicode character. + /// + VolleyballBall = 0xF45F, + + /// + /// The Font Awesome "volume-down" icon unicode character. + /// + VolumeDown = 0xF027, + + /// + /// The Font Awesome "volume-mute" icon unicode character. + /// + VolumeMute = 0xF6A9, + + /// + /// The Font Awesome "volume-off" icon unicode character. + /// + VolumeOff = 0xF026, + + /// + /// The Font Awesome "volume-up" icon unicode character. + /// + VolumeUp = 0xF028, + + /// + /// The Font Awesome "vote-yea" icon unicode character. + /// + VoteYea = 0xF772, + + /// + /// The Font Awesome "vr-cardboard" icon unicode character. + /// + VrCardboard = 0xF729, + + /// + /// The Font Awesome "vuejs" icon unicode character. + /// + Vuejs = 0xF41F, + + /// + /// The Font Awesome "walking" icon unicode character. + /// + Walking = 0xF554, + + /// + /// The Font Awesome "wallet" icon unicode character. + /// + Wallet = 0xF555, + + /// + /// The Font Awesome "warehouse" icon unicode character. + /// + Warehouse = 0xF494, + + /// + /// The Font Awesome "water" icon unicode character. + /// + Water = 0xF773, + + /// + /// The Font Awesome "wave-square" icon unicode character. + /// + WaveSquare = 0xF83E, + + /// + /// The Font Awesome "waze" icon unicode character. + /// + Waze = 0xF83F, + + /// + /// The Font Awesome "weebly" icon unicode character. + /// + Weebly = 0xF5CC, + + /// + /// The Font Awesome "weibo" icon unicode character. + /// + Weibo = 0xF18A, + + /// + /// The Font Awesome "weight" icon unicode character. + /// + Weight = 0xF496, + + /// + /// The Font Awesome "weight-hanging" icon unicode character. + /// + WeightHanging = 0xF5CD, + + /// + /// The Font Awesome "weixin" icon unicode character. + /// + Weixin = 0xF1D7, + + /// + /// The Font Awesome "whatsapp" icon unicode character. + /// + Whatsapp = 0xF232, + + /// + /// The Font Awesome "whatsapp-square" icon unicode character. + /// + WhatsappSquare = 0xF40C, + + /// + /// The Font Awesome "wheelchair" icon unicode character. + /// + Wheelchair = 0xF193, + + /// + /// The Font Awesome "whmcs" icon unicode character. + /// + Whmcs = 0xF40D, + + /// + /// The Font Awesome "wifi" icon unicode character. + /// + Wifi = 0xF1EB, + + /// + /// The Font Awesome "wikipedia-w" icon unicode character. + /// + WikipediaW = 0xF266, + + /// + /// The Font Awesome "wind" icon unicode character. + /// + Wind = 0xF72E, + + /// + /// The Font Awesome "window-close" icon unicode character. + /// + WindowClose = 0xF41, + + /// + /// The Font Awesome "window-maximize" icon unicode character. + /// + WindowMaximize = 0xF2D, + + /// + /// The Font Awesome "window-minimize" icon unicode character. + /// + WindowMinimize = 0xF2D1, + + /// + /// The Font Awesome "window-restore" icon unicode character. + /// + WindowRestore = 0xF2D2, + + /// + /// The Font Awesome "windows" icon unicode character. + /// + Windows = 0xF17A, + + /// + /// The Font Awesome "wine-bottle" icon unicode character. + /// + WineBottle = 0xF72F, + + /// + /// The Font Awesome "wine-glass" icon unicode character. + /// + WineGlass = 0xF4E3, + + /// + /// The Font Awesome "wine-glass-alt" icon unicode character. + /// + WineGlassAlt = 0xF5CE, + + /// + /// The Font Awesome "wix" icon unicode character. + /// + Wix = 0xF5CF, + + /// + /// The Font Awesome "wizards-of-the-coast" icon unicode character. + /// + WizardsOfTheCoast = 0xF73, + + /// + /// The Font Awesome "wolf-pack-battalion" icon unicode character. + /// + WolfPackBattalion = 0xF514, + + /// + /// The Font Awesome "won-sign" icon unicode character. + /// + WonSign = 0xF159, + + /// + /// The Font Awesome "wordpress" icon unicode character. + /// + Wordpress = 0xF19A, + + /// + /// The Font Awesome "wordpress-simple" icon unicode character. + /// + WordpressSimple = 0xF411, + + /// + /// The Font Awesome "wpbeginner" icon unicode character. + /// + Wpbeginner = 0xF297, + + /// + /// The Font Awesome "wpexplorer" icon unicode character. + /// + Wpexplorer = 0xF2DE, + + /// + /// The Font Awesome "wpforms" icon unicode character. + /// + Wpforms = 0xF298, + + /// + /// The Font Awesome "wpressr" icon unicode character. + /// + Wpressr = 0xF3E4, + + /// + /// The Font Awesome "wrench" icon unicode character. + /// + Wrench = 0xF0AD, + + /// + /// The Font Awesome "xbox" icon unicode character. + /// + Xbox = 0xF412, + + /// + /// The Font Awesome "xing" icon unicode character. + /// + Xing = 0xF168, + + /// + /// The Font Awesome "xing-square" icon unicode character. + /// + XingSquare = 0xF169, + + /// + /// The Font Awesome "x-ray" icon unicode character. + /// + XRay = 0xF497, + + /// + /// The Font Awesome "yahoo" icon unicode character. + /// + Yahoo = 0xF19E, + + /// + /// The Font Awesome "yammer" icon unicode character. + /// + Yammer = 0xF84, + + /// + /// The Font Awesome "yandex" icon unicode character. + /// + Yandex = 0xF413, + + /// + /// The Font Awesome "yandex-international" icon unicode character. + /// + YandexInternational = 0xF414, + + /// + /// The Font Awesome "yarn" icon unicode character. + /// + Yarn = 0xF7E3, + + /// + /// The Font Awesome "y-combinator" icon unicode character. + /// + YCombinator = 0xF23B, + + /// + /// The Font Awesome "yelp" icon unicode character. + /// + Yelp = 0xF1E9, + + /// + /// The Font Awesome "yen-sign" icon unicode character. + /// + YenSign = 0xF157, + + /// + /// The Font Awesome "yin-yang" icon unicode character. + /// + YinYang = 0xF6AD, + + /// + /// The Font Awesome "yoast" icon unicode character. + /// + Yoast = 0xF2B1, + + /// + /// The Font Awesome "youtube" icon unicode character. + /// + Youtube = 0xF167, + + /// + /// The Font Awesome "youtube-square" icon unicode character. + /// + YoutubeSquare = 0xF431, + + /// + /// The Font Awesome "zhihu" icon unicode character. + /// + Zhihu = 0xF63F, } + /// + /// Extension methods for . + /// public static class FontAwesomeExtensions { - public static char ToIconChar(this FontAwesomeIcon icon) { - return (char) icon; + /// + /// Convert the FontAwesomeIcon to a type. + /// + /// The icon to convert. + /// The converted icon. + public static char ToIconChar(this FontAwesomeIcon icon) + { + return (char)icon; } + /// + /// Conver the FontAwesomeIcon to a type. + /// + /// The icon to convert. + /// The converted icon. public static string ToIconString(this FontAwesomeIcon icon) { - return "" + (char) icon; + return string.Empty + (char)icon; } } } diff --git a/Dalamud/Interface/GlyphRangesJapanese.cs b/Dalamud/Interface/GlyphRangesJapanese.cs index b32685a45..3c44492ff 100644 --- a/Dalamud/Interface/GlyphRangesJapanese.cs +++ b/Dalamud/Interface/GlyphRangesJapanese.cs @@ -1,10 +1,15 @@ -using System.Runtime.InteropServices; - namespace Dalamud.Interface { - static class GlyphRangesJapanese + /// + /// Unicode glyph ranges for the Japanese language. + /// + public static class GlyphRangesJapanese { - public static ushort[] GlyphRanges => new ushort[] { + /// + /// Gets the unicode glyph ranges for the Japanese language. + /// + public static ushort[] GlyphRanges => new ushort[] + { 0x0020, 0x00FF, 0x0391, 0x03A1, 0x03A3, 0x03A9, 0x03B1, 0x03C1, 0x03C3, 0x03C9, 0x0401, 0x0401, 0x0410, 0x044F, 0x0451, 0x0451, 0x2000, 0x206F, 0x2103, 0x2103, 0x212B, 0x212B, 0x2190, 0x2193, 0x21D2, 0x21D2, 0x21D4, 0x21D4, 0x2200, 0x2200, 0x2202, 0x2203, 0x2207, 0x2208, 0x220B, 0x220B, 0x2212, 0x2212, 0x221A, 0x221A, 0x221D, 0x221E, 0x2220, 0x2220, 0x2227, 0x222C, 0x2234, 0x2235, @@ -520,6 +525,5 @@ namespace Dalamud.Interface 0x9F76, 0x9F77, 0x9F8D, 0x9F8D, 0x9F95, 0x9F95, 0x9F9C, 0x9F9D, 0x9FA0, 0x9FA0, 0xFF01, 0xFF01, 0xFF03, 0xFF06, 0xFF08, 0xFF0C, 0xFF0E, 0xFF3B, 0xFF3D, 0xFF5D, 0xFF61, 0xFF9F, 0xFFE3, 0xFFE3, 0xFFE5, 0xFFE5, 0xFFFF, 0xFFFF, 0, }; - } } diff --git a/Dalamud/Interface/ImGuiHelpers.cs b/Dalamud/Interface/ImGuiHelpers.cs index bfe90b712..d2ecda131 100644 --- a/Dalamud/Interface/ImGuiHelpers.cs +++ b/Dalamud/Interface/ImGuiHelpers.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Numerics; + using ImGuiNET; namespace Dalamud.Interface @@ -50,8 +51,7 @@ namespace Dalamud.Interface /// The position of the next window. /// When to set the position. /// The pivot to set the position around. - public static void SetNextWindowPosRelativeMainViewport( - Vector2 position, ImGuiCond condition = ImGuiCond.None, Vector2 pivot = default) + public static void SetNextWindowPosRelativeMainViewport(Vector2 position, ImGuiCond condition = ImGuiCond.None, Vector2 pivot = default) => ImGui.SetNextWindowPos(position + MainViewport.Pos, condition, pivot); /// diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index 4d7f8ecc4..0f2be86e8 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -5,6 +5,7 @@ using System.Numerics; using System.Runtime.InteropServices; using System.Text; using System.Threading; + using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.Internal.DXGI; @@ -15,8 +16,6 @@ using ImGuiScene; using Serilog; using SharpDX.Direct3D11; -#nullable enable - // general dev notes, here because it's easiest /* * - Hooking ResizeBuffers seemed to be unnecessary, though I'm not sure why. Left out for now since it seems to work without it. @@ -30,30 +29,138 @@ using SharpDX.Direct3D11; namespace Dalamud.Interface { + /// + /// This class manages interaction with the ImGui interface. + /// internal class InterfaceManager : IDisposable { + /// + /// Code that is exexuted when fonts are rebuilt. + /// + public Action OnBuildFonts; + + /// + /// The pointer to ImGui.IO(), when it last used.. + /// + public ImGuiIOPtr LastImGuiIoPtr; + + private readonly Dalamud dalamud; + + private readonly Hook presentHook; + private readonly Hook resizeBuffersHook; + private readonly Hook setCursorHook; + + private ManualResetEvent fontBuildSignal; + private ISwapChainAddressResolver address; + private RawDX11Scene scene; + + private string rtssPath; + + // can't access imgui IO before first present call + private bool lastWantCapture = false; + private bool isRebuildingFonts = false; + + /// + /// Initializes a new instance of the class. + /// + /// The Dalamud instance. + /// The SigScanner instance. + public InterfaceManager(Dalamud dalamud, SigScanner scanner) + { + this.dalamud = dalamud; + + this.fontBuildSignal = new ManualResetEvent(false); + + try + { + var sigResolver = new SwapChainSigResolver(); + sigResolver.Setup(scanner); + + Log.Verbose("Found SwapChain via signatures."); + + this.address = sigResolver; + } + catch (Exception ex) + { + // The SigScanner method fails on wine/proton since DXGI is not a real DLL. We fall back to vtable to detect our Present function address. + Log.Debug(ex, "Could not get SwapChain address via sig method, falling back to vtable..."); + + var vtableResolver = new SwapChainVtableResolver(); + vtableResolver.Setup(scanner); + + Log.Verbose("Found SwapChain via vtable."); + + this.address = vtableResolver; + } + + try + { + var rtss = NativeFunctions.GetModuleHandle("RTSSHooks64.dll"); + + if (rtss != IntPtr.Zero) + { + var fileName = new StringBuilder(255); + NativeFunctions.GetModuleFileName(rtss, fileName, fileName.Capacity); + this.rtssPath = fileName.ToString(); + Log.Verbose("RTSS at {0}", this.rtssPath); + + if (!NativeFunctions.FreeLibrary(rtss)) + throw new Win32Exception(); + } + } + catch (Exception e) + { + Log.Error(e, "RTSS Free failed"); + } + + var setCursorAddr = LocalHook.GetProcAddress("user32.dll", "SetCursor"); + + Log.Verbose("===== S W A P C H A I N ====="); + Log.Verbose("SetCursor address {SetCursor}", setCursorAddr); + Log.Verbose("Present address {Present}", this.address.Present); + Log.Verbose("ResizeBuffers address {ResizeBuffers}", this.address.ResizeBuffers); + + this.setCursorHook = new Hook(setCursorAddr, new SetCursorDelegate(this.SetCursorDetour), this); + + this.presentHook = new Hook(this.address.Present, new PresentDelegate(this.PresentDetour), this); + + this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, new ResizeBuffersDelegate(this.ResizeBuffersDetour), this); + } + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); - private readonly Hook presentHook; - private readonly Hook resizeBuffersHook; - - private readonly Hook setCursorHook; - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr SetCursorDelegate(IntPtr hCursor); - private ManualResetEvent fontBuildSignal; + private delegate void InstallRTSSHook(); - private ISwapChainAddressResolver Address { get; } + /// + /// This event gets called by a plugin UiBuilder when read + /// + public event RawDX11Scene.BuildUIDelegate OnDraw; - private Dalamud dalamud; - private RawDX11Scene scene; + /// + /// Gets the default ImGui font. + /// + public static ImFontPtr DefaultFont { get; private set; } + /// + /// Gets an included FontAwesome icon font. + /// + public static ImFontPtr IconFont { get; private set; } + + /// + /// Gets the D3D11 device instance. + /// public Device Device => this.scene.Device; + + /// + /// Gets the address handle to the main process window. + /// public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr; /// @@ -65,111 +172,45 @@ namespace Dalamud.Interface set => this.scene.UpdateCursor = value; } - private delegate void InstallRTSSHook(); - private string rtssPath; - - public ImGuiIOPtr LastImGuiIoPtr; - - public Action OnBuildFonts; - private bool isRebuildingFonts = false; - /// - /// This event gets called by a plugin UiBuilder when read + /// Gets or sets a value indicating whether the fonts are built and ready to use. /// - public event RawDX11Scene.BuildUIDelegate OnDraw; - public bool FontsReady { get; set; } = false; + /// + /// Gets a value indicating whether the Dalamud interface ready to use. + /// public bool IsReady => this.scene != null; - public InterfaceManager(Dalamud dalamud, SigScanner scanner) - { - this.dalamud = dalamud; - - this.fontBuildSignal = new ManualResetEvent(false); - - try { - var sigResolver = new SwapChainSigResolver(); - sigResolver.Setup(scanner); - - Log.Verbose("Found SwapChain via signatures."); - - Address = sigResolver; - } catch (Exception ex) { - // The SigScanner method fails on wine/proton since DXGI is not a real DLL. We fall back to vtable to detect our Present function address. - Log.Debug(ex, "Could not get SwapChain address via sig method, falling back to vtable..."); - - var vtableResolver = new SwapChainVtableResolver(); - vtableResolver.Setup(scanner); - - Log.Verbose("Found SwapChain via vtable."); - - Address = vtableResolver; - } - - try { - var rtss = NativeFunctions.GetModuleHandle("RTSSHooks64.dll"); - - if (rtss != IntPtr.Zero) { - var fileName = new StringBuilder(255); - NativeFunctions.GetModuleFileName(rtss, fileName, fileName.Capacity); - this.rtssPath = fileName.ToString(); - Log.Verbose("RTSS at {0}", this.rtssPath); - - if (!NativeFunctions.FreeLibrary(rtss)) - throw new Win32Exception(); - } - } catch (Exception e) { - Log.Error(e, "RTSS Free failed"); - } - - - var setCursorAddr = LocalHook.GetProcAddress("user32.dll", "SetCursor"); - - Log.Verbose("===== S W A P C H A I N ====="); - Log.Verbose("SetCursor address {SetCursor}", setCursorAddr); - Log.Verbose("Present address {Present}", Address.Present); - Log.Verbose("ResizeBuffers address {ResizeBuffers}", Address.ResizeBuffers); - - this.setCursorHook = new Hook(setCursorAddr, new SetCursorDelegate(SetCursorDetour), this); - - this.presentHook = - new Hook(Address.Present, - new PresentDelegate(PresentDetour), - this); - - this.resizeBuffersHook = - new Hook(Address.ResizeBuffers, - new ResizeBuffersDelegate(ResizeBuffersDetour), - this); - } - + /// + /// Enable this module. + /// public void Enable() { this.setCursorHook.Enable(); this.presentHook.Enable(); this.resizeBuffersHook.Enable(); - try { - if (!string.IsNullOrEmpty(this.rtssPath)) { + try + { + if (!string.IsNullOrEmpty(this.rtssPath)) + { NativeFunctions.LoadLibrary(this.rtssPath); var installAddr = LocalHook.GetProcAddress("RTSSHooks64.dll", "InstallRTSSHook"); var installDele = Marshal.GetDelegateForFunctionPointer(installAddr); installDele.Invoke(); } - } catch (Exception ex) { + } + catch (Exception ex) + { Log.Error(ex, "Could not reload RTSS"); } } - private void Disable() - { - this.setCursorHook.Disable(); - this.presentHook.Disable(); - this.resizeBuffersHook.Disable(); - } - + /// + /// Dispose of managed and unmanaged resources. + /// public void Dispose() { // HACK: this is usually called on a separate thread from PresentDetour (likely on a dedicated render thread) @@ -179,15 +220,22 @@ namespace Dalamud.Interface // calls to PresentDetour have finished (and Disable means no new ones will start), before we try to cleanup // So... not great, but much better than constantly crashing on unload this.Disable(); - System.Threading.Thread.Sleep(500); - + Thread.Sleep(500); + this.scene?.Dispose(); this.setCursorHook.Dispose(); this.presentHook.Dispose(); this.resizeBuffersHook.Dispose(); } - public TextureWrap LoadImage(string filePath) +#nullable enable + + /// + /// Load an image from disk. + /// + /// The filepath to load. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImage(string filePath) { try { @@ -197,10 +245,16 @@ namespace Dalamud.Interface { Log.Error(ex, $"Failed to load image from {filePath}"); } + return null; } - public TextureWrap LoadImage(byte[] imageData) + /// + /// Load an image from an array of bytes. + /// + /// The data to load. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImage(byte[] imageData) { try { @@ -210,10 +264,19 @@ namespace Dalamud.Interface { Log.Error(ex, "Failed to load image from memory"); } + return null; } - public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) + /// + /// Load an image from an array of bytes. + /// + /// The data to load. + /// The width in pixels. + /// The height in pixels. + /// The number of channels. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImageRaw(byte[] imageData, int width, int height, int numChannels) { try { @@ -223,10 +286,15 @@ namespace Dalamud.Interface { Log.Error(ex, "Failed to load image from raw data"); } + return null; } - // Sets up a deferred invocation of font rebuilding, before the next render frame +#nullable restore + + /// + /// Sets up a deferred invocation of font rebuilding, before the next render frame. + /// public void RebuildFonts() { Log.Verbose("[FONT] RebuildFonts() called"); @@ -237,21 +305,36 @@ namespace Dalamud.Interface Log.Verbose("[FONT] RebuildFonts() trigger"); this.isRebuildingFonts = true; - this.scene.OnNewRenderFrame += RebuildFontsInternal; + this.scene.OnNewRenderFrame += this.RebuildFontsInternal; } } + /// + /// Wait for the rebuilding fonts to complete. + /// + public void WaitForFontRebuild() + { + this.fontBuildSignal.WaitOne(); + } + + private static void ShowFontError(string path) + { + Util.Fatal( + $"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", + "Error"); + } + private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) { - if (this.scene == null) { - + if (this.scene == null) + { this.scene = new RawDX11Scene(swapChain); this.scene.ImGuiIniPath = Path.Combine(Path.GetDirectoryName(this.dalamud.StartInfo.ConfigurationPath), "dalamudUI.ini"); - this.scene.OnBuildUI += Display; - this.scene.OnNewInputFrame += OnNewInputFrame; + this.scene.OnBuildUI += this.Display; + this.scene.OnNewInputFrame += this.OnNewInputFrame; - SetupFonts(); + this.SetupFonts(); ImGui.GetStyle().GrabRounding = 3f; ImGui.GetStyle().FrameRounding = 4f; @@ -260,26 +343,26 @@ namespace Dalamud.Interface ImGui.GetStyle().WindowMenuButtonPosition = ImGuiDir.Right; ImGui.GetStyle().ScrollbarSize = 16f; - ImGui.GetStyle().Colors[(int) ImGuiCol.WindowBg] = new Vector4(0.06f, 0.06f, 0.06f, 0.87f); - ImGui.GetStyle().Colors[(int) ImGuiCol.FrameBg] = new Vector4(0.29f, 0.29f, 0.29f, 0.54f); - ImGui.GetStyle().Colors[(int) ImGuiCol.FrameBgHovered] = new Vector4(0.54f, 0.54f, 0.54f, 0.40f); - ImGui.GetStyle().Colors[(int) ImGuiCol.FrameBgActive] = new Vector4(0.64f, 0.64f, 0.64f, 0.67f); - ImGui.GetStyle().Colors[(int) ImGuiCol.TitleBgActive] = new Vector4(0.29f, 0.29f, 0.29f, 1.00f); - ImGui.GetStyle().Colors[(int) ImGuiCol.CheckMark] = new Vector4(0.86f, 0.86f, 0.86f, 1.00f); - ImGui.GetStyle().Colors[(int) ImGuiCol.SliderGrab] = new Vector4(0.54f, 0.54f, 0.54f, 1.00f); - ImGui.GetStyle().Colors[(int) ImGuiCol.SliderGrabActive] = new Vector4(0.67f, 0.67f, 0.67f, 1.00f); - ImGui.GetStyle().Colors[(int) ImGuiCol.Button] = new Vector4(0.71f, 0.71f, 0.71f, 0.40f); - ImGui.GetStyle().Colors[(int) ImGuiCol.ButtonHovered] = new Vector4(0.47f, 0.47f, 0.47f, 1.00f); - ImGui.GetStyle().Colors[(int) ImGuiCol.ButtonActive] = new Vector4(0.74f, 0.74f, 0.74f, 1.00f); - ImGui.GetStyle().Colors[(int) ImGuiCol.Header] = new Vector4(0.59f, 0.59f, 0.59f, 0.31f); - ImGui.GetStyle().Colors[(int) ImGuiCol.HeaderHovered] = new Vector4(0.50f, 0.50f, 0.50f, 0.80f); - ImGui.GetStyle().Colors[(int) ImGuiCol.HeaderActive] = new Vector4(0.60f, 0.60f, 0.60f, 1.00f); - ImGui.GetStyle().Colors[(int) ImGuiCol.ResizeGrip] = new Vector4(0.79f, 0.79f, 0.79f, 0.25f); - ImGui.GetStyle().Colors[(int) ImGuiCol.ResizeGripHovered] = new Vector4(0.78f, 0.78f, 0.78f, 0.67f); - ImGui.GetStyle().Colors[(int) ImGuiCol.ResizeGripActive] = new Vector4(0.88f, 0.88f, 0.88f, 0.95f); - ImGui.GetStyle().Colors[(int) ImGuiCol.Tab] = new Vector4(0.23f, 0.23f, 0.23f, 0.86f); - ImGui.GetStyle().Colors[(int) ImGuiCol.TabHovered] = new Vector4(0.71f, 0.71f, 0.71f, 0.80f); - ImGui.GetStyle().Colors[(int) ImGuiCol.TabActive] = new Vector4(0.36f, 0.36f, 0.36f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.WindowBg] = new Vector4(0.06f, 0.06f, 0.06f, 0.87f); + ImGui.GetStyle().Colors[(int)ImGuiCol.FrameBg] = new Vector4(0.29f, 0.29f, 0.29f, 0.54f); + ImGui.GetStyle().Colors[(int)ImGuiCol.FrameBgHovered] = new Vector4(0.54f, 0.54f, 0.54f, 0.40f); + ImGui.GetStyle().Colors[(int)ImGuiCol.FrameBgActive] = new Vector4(0.64f, 0.64f, 0.64f, 0.67f); + ImGui.GetStyle().Colors[(int)ImGuiCol.TitleBgActive] = new Vector4(0.29f, 0.29f, 0.29f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.CheckMark] = new Vector4(0.86f, 0.86f, 0.86f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.SliderGrab] = new Vector4(0.54f, 0.54f, 0.54f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.SliderGrabActive] = new Vector4(0.67f, 0.67f, 0.67f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.Button] = new Vector4(0.71f, 0.71f, 0.71f, 0.40f); + ImGui.GetStyle().Colors[(int)ImGuiCol.ButtonHovered] = new Vector4(0.47f, 0.47f, 0.47f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.ButtonActive] = new Vector4(0.74f, 0.74f, 0.74f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.Header] = new Vector4(0.59f, 0.59f, 0.59f, 0.31f); + ImGui.GetStyle().Colors[(int)ImGuiCol.HeaderHovered] = new Vector4(0.50f, 0.50f, 0.50f, 0.80f); + ImGui.GetStyle().Colors[(int)ImGuiCol.HeaderActive] = new Vector4(0.60f, 0.60f, 0.60f, 1.00f); + ImGui.GetStyle().Colors[(int)ImGuiCol.ResizeGrip] = new Vector4(0.79f, 0.79f, 0.79f, 0.25f); + ImGui.GetStyle().Colors[(int)ImGuiCol.ResizeGripHovered] = new Vector4(0.78f, 0.78f, 0.78f, 0.67f); + ImGui.GetStyle().Colors[(int)ImGuiCol.ResizeGripActive] = new Vector4(0.88f, 0.88f, 0.88f, 0.95f); + ImGui.GetStyle().Colors[(int)ImGuiCol.Tab] = new Vector4(0.23f, 0.23f, 0.23f, 0.86f); + ImGui.GetStyle().Colors[(int)ImGuiCol.TabHovered] = new Vector4(0.71f, 0.71f, 0.71f, 0.80f); + ImGui.GetStyle().Colors[(int)ImGuiCol.TabActive] = new Vector4(0.36f, 0.36f, 0.36f, 1.00f); ImGui.GetIO().FontGlobalScale = this.dalamud.Configuration.GlobalUiScale; @@ -334,16 +417,6 @@ namespace Dalamud.Interface ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; } - public static ImFontPtr DefaultFont { get; private set; } - public static ImFontPtr IconFont { get; private set; } - - private static void ShowFontError(string path) - { - Util.Fatal( - $"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", - "Error"); - } - private unsafe void SetupFonts() { this.fontBuildSignal.Reset(); @@ -368,12 +441,14 @@ namespace Dalamud.Interface if (!File.Exists(fontPathGame)) ShowFontError(fontPathGame); - var gameRangeHandle = GCHandle.Alloc(new ushort[] - { - 0xE020, - 0xE0DB, - 0 - }, GCHandleType.Pinned); + var gameRangeHandle = GCHandle.Alloc( + new ushort[] + { + 0xE020, + 0xE0DB, + 0, + }, + GCHandleType.Pinned); ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathGame, 17.0f, fontConfig, gameRangeHandle.AddrOfPinnedObject()); @@ -382,19 +457,22 @@ namespace Dalamud.Interface if (!File.Exists(fontPathIcon)) ShowFontError(fontPathIcon); - var iconRangeHandle = GCHandle.Alloc(new ushort[] - { - 0xE000, - 0xF8FF, - 0 - }, GCHandleType.Pinned); + var iconRangeHandle = GCHandle.Alloc( + new ushort[] + { + 0xE000, + 0xF8FF, + 0, + }, + GCHandleType.Pinned); IconFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathIcon, 17.0f, null, iconRangeHandle.AddrOfPinnedObject()); Log.Verbose("[FONT] Invoke OnBuildFonts"); this.OnBuildFonts?.Invoke(); Log.Verbose("[FONT] OnBuildFonts OK!"); - for (var i = 0; i < ImGui.GetIO().Fonts.Fonts.Size; i++) { + for (var i = 0; i < ImGui.GetIO().Fonts.Fonts.Size; i++) + { Log.Verbose("{0} - {1}", i, ImGui.GetIO().Fonts.Fonts[i].GetDebugName()); } @@ -412,18 +490,21 @@ namespace Dalamud.Interface this.FontsReady = true; } - public void WaitForFontRebuild() { - this.fontBuildSignal.WaitOne(); + private void Disable() + { + this.setCursorHook.Disable(); + this.presentHook.Disable(); + this.resizeBuffersHook.Disable(); } // This is intended to only be called as a handler attached to scene.OnNewRenderFrame private void RebuildFontsInternal() { Log.Verbose("[FONT] RebuildFontsInternal() called"); - SetupFonts(); + this.SetupFonts(); Log.Verbose("[FONT] RebuildFontsInternal() detaching"); - this.scene.OnNewRenderFrame -= RebuildFontsInternal; + this.scene.OnNewRenderFrame -= this.RebuildFontsInternal; this.scene.InvalidateFonts(); Log.Verbose("[FONT] Font Rebuild OK!"); @@ -440,7 +521,7 @@ namespace Dalamud.Interface // We have to ensure we're working with the main swapchain, // as viewports might be resizing as well if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) - return resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + return this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); this.scene?.OnPreResize(); @@ -455,11 +536,9 @@ namespace Dalamud.Interface return ret; } - // can't access imgui IO before first present call - private bool lastWantCapture = false; - - private IntPtr SetCursorDetour(IntPtr hCursor) { - if (this.lastWantCapture == true && (!scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) + private IntPtr SetCursorDetour(IntPtr hCursor) + { + if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) return IntPtr.Zero; return this.setCursorHook.Original(hCursor); @@ -527,11 +606,11 @@ namespace Dalamud.Interface // If the player has the game software cursor enabled, we can't really do anything about that and // they will see both cursors. // Doing this here because it's somewhat application-specific behavior - //ImGui.GetIO().MouseDrawCursor = ImGui.GetIO().WantCaptureMouse; + // ImGui.GetIO().MouseDrawCursor = ImGui.GetIO().WantCaptureMouse; this.LastImGuiIoPtr = ImGui.GetIO(); this.lastWantCapture = this.LastImGuiIoPtr.WantCaptureMouse; - OnDraw?.Invoke(); + this.OnDraw?.Invoke(); } } } diff --git a/Dalamud/Interface/SerilogEventSink.cs b/Dalamud/Interface/SerilogEventSink.cs index 84f9806df..4810f75b8 100644 --- a/Dalamud/Interface/SerilogEventSink.cs +++ b/Dalamud/Interface/SerilogEventSink.cs @@ -1,49 +1,49 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; -using Serilog; -using Serilog.Configuration; + using Serilog.Core; using Serilog.Events; namespace Dalamud.Interface { + /// + /// Serilog event sink. + /// internal class SerilogEventSink : ILogEventSink { - private readonly IFormatProvider _formatProvider; + private static SerilogEventSink instance; + private readonly IFormatProvider formatProvider; - public static SerilogEventSink Instance; - - public event EventHandler<(string line, LogEventLevel level)> OnLogLine; - - public SerilogEventSink(IFormatProvider formatProvider) + /// + /// Initializes a new instance of the class. + /// + /// Logging format provider. + private SerilogEventSink(IFormatProvider formatProvider) { - _formatProvider = formatProvider; - - Instance = this; + this.formatProvider = formatProvider; } + /// + /// Event on a log line being emitted. + /// + public event EventHandler<(string Line, LogEventLevel Level)> OnLogLine; + + /// + /// Gets the default instance. + /// + public static SerilogEventSink Instance => instance ??= new SerilogEventSink(null); + + /// + /// Emit a log event. + /// + /// Log event to be emitted. public void Emit(LogEvent logEvent) { - var message = $"[{DateTimeOffset.Now:HH:mm:ss.fff}][{logEvent.Level}] {logEvent.RenderMessage(_formatProvider)}"; + var message = $"[{DateTimeOffset.Now:HH:mm:ss.fff}][{logEvent.Level}] {logEvent.RenderMessage(this.formatProvider)}"; if (logEvent.Exception != null) message += "\n" + logEvent.Exception; - OnLogLine?.Invoke(this, (message, logEvent.Level)); - } - } - - public static class MySinkExtensions - { - public static LoggerConfiguration EventSink( - this LoggerSinkConfiguration loggerConfiguration, - IFormatProvider formatProvider = null) - { - return loggerConfiguration.Sink(new SerilogEventSink(formatProvider)); + this.OnLogLine?.Invoke(this, (message, logEvent.Level)); } } } diff --git a/Dalamud/Interface/UiDebug.cs b/Dalamud/Interface/UiDebug.cs index 301f21ba1..16e2a75fc 100644 --- a/Dalamud/Interface/UiDebug.cs +++ b/Dalamud/Interface/UiDebug.cs @@ -9,21 +9,25 @@ using System.Runtime.InteropServices; using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI.ULD; using ImGuiNET; + using AlignmentType = FFXIVClientStructs.FFXIV.Component.GUI.AlignmentType; // Customised version of https://github.com/aers/FFXIVUIDebug -namespace Dalamud.Interface { - - internal unsafe class UIDebug { - private AtkUnitBase* selectedUnitBase = null; - - private delegate AtkStage* GetAtkStageSingleton(); - private readonly GetAtkStageSingleton getAtkStageSingleton; - +namespace Dalamud.Interface +{ + /// + /// This class displays a debug window to inspect native addons. + /// + internal unsafe class UIDebug + { private const int UnitListCount = 18; + + private readonly Dalamud dalamud; + private readonly GetAtkStageSingleton getAtkStageSingleton; private readonly bool[] selectedInList = new bool[UnitListCount]; - private readonly string[] listNames = new string[UnitListCount]{ + private readonly string[] listNames = new string[UnitListCount] + { "Depth Layer 1", "Depth Layer 2", "Depth Layer 3", @@ -41,39 +45,67 @@ namespace Dalamud.Interface { "Focused Units", "Units 16", "Units 17", - "Units 18" + "Units 18", }; + private bool doingSearch; private string searchInput = string.Empty; - private readonly Dalamud dalamud; - + private AtkUnitBase* selectedUnitBase = null; + private ulong beginModule; + private ulong endModule; + + /// + /// Initializes a new instance of the class. + /// + /// The Dalamud instance. public UIDebug(Dalamud dalamud) { this.dalamud = dalamud; var getSingletonAddr = dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF"); this.getAtkStageSingleton = Marshal.GetDelegateForFunctionPointer(getSingletonAddr); } - - - public void Draw() { + + private delegate AtkStage* GetAtkStageSingleton(); + + /// + /// Renders this window. + /// + public void Draw() + { ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); ImGui.BeginChild("st_uiDebug_unitBaseSelect", new Vector2(250, -1), true); - + ImGui.SetNextItemWidth(-1); ImGui.InputTextWithHint("###atkUnitBaseSearch", "Search", ref this.searchInput, 0x20); - - DrawUnitBaseList(); + + this.DrawUnitBaseList(); ImGui.EndChild(); - if (selectedUnitBase != null) { + if (this.selectedUnitBase != null) + { ImGui.SameLine(); ImGui.BeginChild("st_uiDebug_selectedUnitBase", new Vector2(-1, -1), true); - DrawUnitBase(selectedUnitBase); + this.DrawUnitBase(this.selectedUnitBase); ImGui.EndChild(); } + ImGui.PopStyleVar(); } - - private void DrawUnitBase(AtkUnitBase* atkUnitBase) { + + private static void ClickToCopyText(string text, string textCopy = null) + { + textCopy ??= text; + ImGui.Text($"{text}"); + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + if (textCopy != text) ImGui.SetTooltip(textCopy); + } + + if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); + } + + private void DrawUnitBase(AtkUnitBase* atkUnitBase) + { var isVisible = (atkUnitBase->Flags & 0x20) == 0x20; var addonName = Marshal.PtrToStringAnsi(new IntPtr(atkUnitBase->Name)); var agent = this.dalamud.Framework.Gui.FindAgentInterface((IntPtr)atkUnitBase); @@ -83,75 +115,78 @@ namespace Dalamud.Interface { ImGui.PushStyleColor(ImGuiCol.Text, isVisible ? 0xFF00FF00 : 0xFF0000FF); ImGui.Text(isVisible ? "Visible" : "Not Visible"); ImGui.PopStyleColor(); - + ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - 25); - if (ImGui.SmallButton("V")) { + if (ImGui.SmallButton("V")) + { atkUnitBase->Flags ^= 0x20; } - + ImGui.Separator(); - ClickToCopyText($"Address: {(ulong) atkUnitBase:X}", $"{(ulong) atkUnitBase:X}"); + ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); ImGui.Separator(); - + ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]"); - ImGui.Text($"Scale: {atkUnitBase->Scale*100}%%"); - ImGui.Text($"Widget Count {atkUnitBase->ULDData.ObjectCount}"); - + ImGui.Text($"Scale: {atkUnitBase->Scale * 100}%%"); + ImGui.Text($"Widget Count {atkUnitBase->UldManager.ObjectCount}"); + ImGui.Separator(); object addonObj = *atkUnitBase; - - PrintOutObject(addonObj, (ulong) atkUnitBase, new List()); + this.PrintOutObject(addonObj, (ulong)atkUnitBase, new List()); ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); ImGui.Separator(); if (atkUnitBase->RootNode != null) - PrintNode(atkUnitBase->RootNode); + this.PrintNode(atkUnitBase->RootNode); - - if (atkUnitBase->ULDData.NodeListCount > 0) { + if (atkUnitBase->UldManager.NodeListCount > 0) + { ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); ImGui.Separator(); ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); - if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) { + if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) + { ImGui.PopStyleColor(); - for (var j = 0; j < atkUnitBase->ULDData.NodeListCount; j++) { - PrintNode(atkUnitBase->ULDData.NodeList[j], false, $"[{j}] "); + for (var j = 0; j < atkUnitBase->UldManager.NodeListCount; j++) + { + this.PrintNode(atkUnitBase->UldManager.NodeList[j], false, $"[{j}] "); } ImGui.TreePop(); - } else { + } + else + { ImGui.PopStyleColor(); } } } - private void PrintNode(AtkResNode* node, bool printSiblings = true, string treePrefix = "") { if (node == null) return; if ((int)node->Type < 1000) - PrintSimpleNode(node, treePrefix); + this.PrintSimpleNode(node, treePrefix); else - PrintComponentNode(node, treePrefix); + this.PrintComponentNode(node, treePrefix); if (printSiblings) { var prevNode = node; while ((prevNode = prevNode->PrevSiblingNode) != null) - PrintNode(prevNode, false, "prev "); + this.PrintNode(prevNode, false, "prev "); var nextNode = node; while ((nextNode = nextNode->NextSiblingNode) != null) - PrintNode(nextNode, false, "next "); + this.PrintNode(nextNode, false, "next "); } } - + private void PrintSimpleNode(AtkResNode* node, string treePrefix) { var popped = false; @@ -162,7 +197,9 @@ namespace Dalamud.Interface { if (ImGui.TreeNode($"{treePrefix}{node->Type} Node (ptr = {(long)node:X})###{(long)node}")) { - if (ImGui.IsItemHovered()) DrawOutline(node); + if (ImGui.IsItemHovered()) + this.DrawOutline(node); + if (isVisible) { ImGui.PopStyleColor(); @@ -173,21 +210,20 @@ namespace Dalamud.Interface { ImGui.SameLine(); ClickToCopyText($"{(ulong)node:X}"); ImGui.SameLine(); - switch (node->Type) { - case NodeType.Text: PrintOutObject(*(AtkTextNode*)node, (ulong) node, new List()); break; - case NodeType.Image: PrintOutObject(*(AtkImageNode*)node, (ulong) node, new List()); break; - case NodeType.Collision: PrintOutObject(*(AtkCollisionNode*)node, (ulong) node, new List()); break; - case NodeType.NineGrid: PrintOutObject(*(AtkNineGridNode*)node, (ulong) node, new List()); break; - case NodeType.Counter: PrintOutObject(*(AtkCounterNode*)node, (ulong) node, new List()); break; - default: PrintOutObject(*node, (ulong) node, new List()); break; + switch (node->Type) + { + case NodeType.Text: this.PrintOutObject(*(AtkTextNode*)node, (ulong)node, new List()); break; + case NodeType.Image: this.PrintOutObject(*(AtkImageNode*)node, (ulong)node, new List()); break; + case NodeType.Collision: this.PrintOutObject(*(AtkCollisionNode*)node, (ulong)node, new List()); break; + case NodeType.NineGrid: this.PrintOutObject(*(AtkNineGridNode*)node, (ulong)node, new List()); break; + case NodeType.Counter: this.PrintOutObject(*(AtkCounterNode*)node, (ulong)node, new List()); break; + default: this.PrintOutObject(*node, (ulong)node, new List()); break; } - - PrintResNode(node); + + this.PrintResNode(node); if (node->ChildNode != null) - { - PrintNode(node->ChildNode); - } + this.PrintNode(node->ChildNode); switch (node->Type) { @@ -195,18 +231,18 @@ namespace Dalamud.Interface { var textNode = (AtkTextNode*)node; ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(textNode->NodeText.StringPtr))}"); - ImGui.InputText($"Replace Text##{(ulong) textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint) textNode->NodeText.BufSize); - + ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); int b = textNode->AlignmentFontType; - if (ImGui.InputInt($"###setAlignment{(ulong) textNode:X}", ref b, 1)) { + if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) + { while (b > byte.MaxValue) b -= byte.MaxValue; while (b < byte.MinValue) b += byte.MaxValue; - textNode->AlignmentFontType = (byte) b; + textNode->AlignmentFontType = (byte)b; textNode->AtkResNode.Flags_2 |= 0x1; } - + ImGui.Text($"Color: #{textNode->TextColor.R:X2}{textNode->TextColor.G:X2}{textNode->TextColor.B:X2}{textNode->TextColor.A:X2}"); ImGui.SameLine(); ImGui.Text($"EdgeColor: #{textNode->EdgeColor.R:X2}{textNode->EdgeColor.G:X2}{textNode->EdgeColor.B:X2}{textNode->EdgeColor.A:X2}"); @@ -217,8 +253,6 @@ namespace Dalamud.Interface { ImGui.SameLine(); ImGui.Text($"TextFlags2: {textNode->TextFlags2}"); - - break; case NodeType.Counter: var counterNode = (AtkCounterNode*)node; @@ -226,39 +260,54 @@ namespace Dalamud.Interface { break; case NodeType.Image: var imageNode = (AtkImageNode*)node; - if (imageNode->PartsList != null) { - if (imageNode->PartId > imageNode->PartsList->PartCount) { + if (imageNode->PartsList != null) + { + if (imageNode->PartId > imageNode->PartsList->PartCount) + { ImGui.Text("part id > part count?"); - } else { + } + else + { var textureInfo = imageNode->PartsList->Parts[imageNode->PartId].UldAsset; var texType = textureInfo->AtkTexture.TextureType; ImGui.Text($"texture type: {texType} part_id={imageNode->PartId} part_id_count={imageNode->PartsList->PartCount}"); - if (texType == TextureType.Resource) { + if (texType == TextureType.Resource) + { var texFileNamePtr = textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; var texString = Marshal.PtrToStringAnsi(new IntPtr(texFileNamePtr)); ImGui.Text($"texture path: {texString}"); var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; - - if (ImGui.TreeNode($"Texture##{(ulong) kernelTexture->D3D11ShaderResourceView:X}")) { + + if (ImGui.TreeNode($"Texture##{(ulong)kernelTexture->D3D11ShaderResourceView:X}")) + { ImGui.Image(new IntPtr(kernelTexture->D3D11ShaderResourceView), new Vector2(kernelTexture->Width, kernelTexture->Height)); ImGui.TreePop(); } - } else if (texType == TextureType.KernelTexture) { - if (ImGui.TreeNode($"Texture##{(ulong) textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView:X}")) { - ImGui.Image(new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2(textureInfo->AtkTexture.KernelTexture->Width, textureInfo->AtkTexture.KernelTexture->Height)); + } + else if (texType == TextureType.KernelTexture) + { + if (ImGui.TreeNode($"Texture##{(ulong)textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView:X}")) + { + ImGui.Image(new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2(textureInfo->AtkTexture.KernelTexture->Width, textureInfo->AtkTexture.KernelTexture->Height)); ImGui.TreePop(); } } } - } else { + } + else + { ImGui.Text("no texture loaded"); } + break; } ImGui.TreePop(); } - else if(ImGui.IsItemHovered()) DrawOutline(node); + else if (ImGui.IsItemHovered()) + { + this.DrawOutline(node); + } if (isVisible && !popped) ImGui.PopStyleColor(); @@ -268,20 +317,22 @@ namespace Dalamud.Interface { { var compNode = (AtkComponentNode*)node; - bool popped = false; - bool isVisible = (node->Flags & 0x10) == 0x10; + var popped = false; + var isVisible = (node->Flags & 0x10) == 0x10; if (isVisible) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); - var componentInfo = compNode->Component->ULDData; + var componentInfo = compNode->Component->UldManager; var childCount = componentInfo.NodeListCount; var objectInfo = (ULDComponentInfo*)componentInfo.Objects; if (ImGui.TreeNode($"{treePrefix}{objectInfo->ComponentType} Component Node (ptr = {(long)node:X}, component ptr = {(long)compNode->Component:X}) child count = {childCount} ###{(long)node}")) { - if (ImGui.IsItemHovered()) DrawOutline(node); + if (ImGui.IsItemHovered()) + this.DrawOutline(node); + if (isVisible) { ImGui.PopStyleColor(); @@ -292,26 +343,27 @@ namespace Dalamud.Interface { ImGui.SameLine(); ClickToCopyText($"{(ulong)node:X}"); ImGui.SameLine(); - PrintOutObject(*compNode, (ulong) compNode, new List()); + this.PrintOutObject(*compNode, (ulong)compNode, new List()); ImGui.Text("Component: "); ImGui.SameLine(); ClickToCopyText($"{(ulong)compNode->Component:X}"); ImGui.SameLine(); - - switch (objectInfo->ComponentType) { - case ComponentType.Button: PrintOutObject(*(AtkComponentButton*)compNode->Component, (ulong) compNode->Component, new List()); break; - case ComponentType.Slider: PrintOutObject(*(AtkComponentSlider*)compNode->Component, (ulong) compNode->Component, new List()); break; - case ComponentType.Window: PrintOutObject(*(AtkComponentWindow*)compNode->Component, (ulong) compNode->Component, new List()); break; - case ComponentType.CheckBox: PrintOutObject(*(AtkComponentCheckBox*)compNode->Component, (ulong) compNode->Component, new List()); break; - case ComponentType.GaugeBar: PrintOutObject(*(AtkComponentGaugeBar*)compNode->Component, (ulong) compNode->Component, new List()); break; - case ComponentType.RadioButton: PrintOutObject(*(AtkComponentRadioButton*)compNode->Component, (ulong) compNode->Component, new List()); break; - case ComponentType.TextInput: PrintOutObject(*(AtkComponentTextInput*)compNode->Component, (ulong) compNode->Component, new List()); break; - case ComponentType.Icon: PrintOutObject(*(AtkComponentIcon*)compNode->Component, (ulong) compNode->Component, new List()); break; - default: PrintOutObject(*compNode->Component, (ulong) compNode->Component, new List()); break; + + switch (objectInfo->ComponentType) + { + case ComponentType.Button: this.PrintOutObject(*(AtkComponentButton*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Slider: this.PrintOutObject(*(AtkComponentSlider*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Window: this.PrintOutObject(*(AtkComponentWindow*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.CheckBox: this.PrintOutObject(*(AtkComponentCheckBox*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.GaugeBar: this.PrintOutObject(*(AtkComponentGaugeBar*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.RadioButton: this.PrintOutObject(*(AtkComponentRadioButton*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.TextInput: this.PrintOutObject(*(AtkComponentTextInput*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Icon: this.PrintOutObject(*(AtkComponentIcon*)compNode->Component, (ulong)compNode->Component, new List()); break; + default: this.PrintOutObject(*compNode->Component, (ulong)compNode->Component, new List()); break; } - PrintResNode(node); - PrintNode(componentInfo.RootNode); + this.PrintResNode(node); + this.PrintNode(componentInfo.RootNode); switch (objectInfo->ComponentType) { @@ -328,40 +380,48 @@ namespace Dalamud.Interface { } ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); - if (ImGui.TreeNode($"Node List##{(ulong) node:X}")) { + if (ImGui.TreeNode($"Node List##{(ulong)node:X}")) + { ImGui.PopStyleColor(); - for (var i = 0; i < compNode->Component->ULDData.NodeListCount; i++) { - PrintNode(compNode->Component->ULDData.NodeList[i], false, $"[{i}] "); + for (var i = 0; i < compNode->Component->UldManager.NodeListCount; i++) + { + this.PrintNode(compNode->Component->UldManager.NodeList[i], false, $"[{i}] "); } ImGui.TreePop(); - } else { + } + else + { ImGui.PopStyleColor(); } ImGui.TreePop(); } - else if (ImGui.IsItemHovered()) DrawOutline(node); - + else if (ImGui.IsItemHovered()) + { + this.DrawOutline(node); + } if (isVisible && !popped) ImGui.PopStyleColor(); } - + private void PrintResNode(AtkResNode* node) { ImGui.Text($"NodeID: {node->NodeID}"); ImGui.SameLine(); - if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) { + if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) + { node->Flags ^= 0x10; } + ImGui.SameLine(); - if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) { + if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) + { ImGui.SetClipboardText($"{(ulong)node:X}"); } - ImGui.Text( $"X: {node->X} Y: {node->Y} " + $"ScaleX: {node->ScaleX} ScaleY: {node->ScaleY} " + @@ -374,252 +434,297 @@ namespace Dalamud.Interface { $"MultiplyRGB: {node->MultiplyRed} {node->MultiplyGreen} {node->MultiplyBlue}"); } - - private bool doingSearch; - - private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight) { + private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight) + { ImGui.PushStyleColor(ImGuiCol.Text, highlight ? 0xFFAAAA00 : 0xFFFFFFFF); - if (!string.IsNullOrEmpty(this.searchInput) && !doingSearch) { + if (!string.IsNullOrEmpty(this.searchInput) && !this.doingSearch) + { ImGui.SetNextItemOpen(true, ImGuiCond.Always); - } else if (doingSearch && string.IsNullOrEmpty(this.searchInput)) { + } + else if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) + { ImGui.SetNextItemOpen(false, ImGuiCond.Always); } - var treeNode = ImGui.TreeNode($"{listNames[index]}##unitList_{index}"); + + var treeNode = ImGui.TreeNode($"{this.listNames[index]}##unitList_{index}"); ImGui.PopStyleColor(); - + ImGui.SameLine(); ImGui.TextDisabled($"C:{count} {ptr:X}"); return treeNode; } - private void DrawUnitBaseList() { + private void DrawUnitBaseList() + { + var foundSelected = false; + var noResults = true; + var stage = this.getAtkStageSingleton(); - bool foundSelected = false; - bool noResults = true; - var stage = getAtkStageSingleton(); - var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList; var searchStr = this.searchInput; var searching = !string.IsNullOrEmpty(searchStr); - - for (var i = 0; i < UnitListCount; i++) { + for (var i = 0; i < UnitListCount; i++) + { var headerDrawn = false; - - var highlight = selectedUnitBase != null && selectedInList[i]; - selectedInList[i] = false; + + var highlight = this.selectedUnitBase != null && this.selectedInList[i]; + this.selectedInList[i] = false; var unitManager = &unitManagers[i]; - var unitBaseArray = &(unitManager->AtkUnitEntries); + var unitBaseArray = &unitManager->AtkUnitEntries; var headerOpen = true; - - if (!searching) { - headerOpen = DrawUnitListHeader(i, unitManager->Count, (ulong) unitManager, highlight); + + if (!searching) + { + headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); headerDrawn = true; noResults = false; } - - for (var j = 0; j < unitManager->Count && headerOpen; j++) { + + for (var j = 0; j < unitManager->Count && headerOpen; j++) + { var unitBase = unitBaseArray[j]; - if (selectedUnitBase != null && unitBase == selectedUnitBase) { - selectedInList[i] = true; + if (this.selectedUnitBase != null && unitBase == this.selectedUnitBase) + { + this.selectedInList[i] = true; foundSelected = true; } + var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name)); - if (searching) { + if (searching) + { if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue; } + noResults = false; - if (!headerDrawn) { - headerOpen = DrawUnitListHeader(i, unitManager->Count, (ulong) unitManager, highlight); + if (!headerDrawn) + { + headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); headerDrawn = true; } - - if (headerOpen) { + + if (headerOpen) + { var visible = (unitBase->Flags & 0x20) == 0x20; ImGui.PushStyleColor(ImGuiCol.Text, visible ? 0xFF00FF00 : 0xFF999999); - - if (ImGui.Selectable($"{name}##list{i}-{(ulong) unitBase:X}_{j}", selectedUnitBase == unitBase)) { - selectedUnitBase = unitBase; + + if (ImGui.Selectable($"{name}##list{i}-{(ulong)unitBase:X}_{j}", this.selectedUnitBase == unitBase)) + { + this.selectedUnitBase = unitBase; foundSelected = true; - selectedInList[i] = true; + this.selectedInList[i] = true; } + ImGui.PopStyleColor(); } - } - if (headerDrawn && headerOpen) { + if (headerDrawn && headerOpen) + { ImGui.TreePop(); } - - if (selectedInList[i] == false && selectedUnitBase != null) { - for (var j = 0; j < unitManager->Count; j++) { - if (selectedUnitBase == null || unitBaseArray[j] != selectedUnitBase) continue; - selectedInList[i] = true; + + if (this.selectedInList[i] == false && this.selectedUnitBase != null) + { + for (var j = 0; j < unitManager->Count; j++) + { + if (this.selectedUnitBase == null || unitBaseArray[j] != this.selectedUnitBase) continue; + this.selectedInList[i] = true; foundSelected = true; } } - } - if (noResults) { + if (noResults) + { ImGui.TextDisabled("No Results"); } - - if (!foundSelected) { - selectedUnitBase = null; + + if (!foundSelected) + { + this.selectedUnitBase = null; } - - if (doingSearch && string.IsNullOrEmpty(this.searchInput)) { - doingSearch = false; - } else if (!doingSearch && !string.IsNullOrEmpty(this.searchInput)) { - doingSearch = true; + if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) + { + this.doingSearch = false; + } + else if (!this.doingSearch && !string.IsNullOrEmpty(this.searchInput)) + { + this.doingSearch = true; } } - - - private Vector2 GetNodePosition(AtkResNode* node) { + + private Vector2 GetNodePosition(AtkResNode* node) + { var pos = new Vector2(node->X, node->Y); var par = node->ParentNode; - while (par != null) { + while (par != null) + { pos *= new Vector2(par->ScaleX, par->ScaleY); pos += new Vector2(par->X, par->Y); par = par->ParentNode; } + return pos; } - private Vector2 GetNodeScale(AtkResNode* node) { + private Vector2 GetNodeScale(AtkResNode* node) + { if (node == null) return new Vector2(1, 1); var scale = new Vector2(node->ScaleX, node->ScaleY); - while (node->ParentNode != null) { + while (node->ParentNode != null) + { node = node->ParentNode; scale *= new Vector2(node->ScaleX, node->ScaleY); } + return scale; } - private bool GetNodeVisible(AtkResNode* node) { + private bool GetNodeVisible(AtkResNode* node) + { if (node == null) return false; - while (node != null) { + while (node != null) + { if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false; node = node->ParentNode; } + return true; } - private void DrawOutline(AtkResNode* node) { - var position = GetNodePosition(node); - var scale = GetNodeScale(node); + private void DrawOutline(AtkResNode* node) + { + var position = this.GetNodePosition(node); + var scale = this.GetNodeScale(node); var size = new Vector2(node->Width, node->Height) * scale; - - var nodeVisible = GetNodeVisible(node); + + var nodeVisible = this.GetNodeVisible(node); position += ImGuiHelpers.MainViewport.Pos; ImGui.GetForegroundDrawList(ImGuiHelpers.MainViewport).AddRect(position, position + size, nodeVisible ? 0xFF00FF00 : 0xFF0000FF); } - - private static void ClickToCopyText(string text, string textCopy = null) { - textCopy ??= text; - ImGui.Text($"{text}"); - if (ImGui.IsItemHovered()) { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - if (textCopy != text) ImGui.SetTooltip(textCopy); - } - if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); - } - - private void PrintOutValue(ulong addr, IEnumerable path, Type type, object value) { - if (type.IsPointer) { - var val = (Pointer) value; + + private void PrintOutValue(ulong addr, IEnumerable path, Type type, object value) + { + if (type.IsPointer) + { + var val = (Pointer)value; var unboxed = Pointer.Unbox(val); - if (unboxed != null) { - var unboxedAddr = (ulong) unboxed; + if (unboxed != null) + { + var unboxedAddr = (ulong)unboxed; ClickToCopyText($"{(ulong)unboxed:X}"); - if (beginModule > 0 && unboxedAddr >= this.beginModule && unboxedAddr <= this.endModule) { + if (this.beginModule > 0 && unboxedAddr >= this.beginModule && unboxedAddr <= this.endModule) + { ImGui.SameLine(); ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ClickToCopyText($"ffxiv_dx11.exe+{(unboxedAddr-this.beginModule):X}"); + ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - this.beginModule:X}"); ImGui.PopStyleColor(); } - try { + + try + { var eType = type.GetElementType(); var ptrObj = Marshal.PtrToStructure(new IntPtr(unboxed), eType); ImGui.SameLine(); - PrintOutObject(ptrObj, (ulong) unboxed, new List(path)); - } catch { + this.PrintOutObject(ptrObj, (ulong)unboxed, new List(path)); + } + catch + { // Ignored } - } else { + } + else + { ImGui.Text("null"); } - } else { - if (!type.IsPrimitive) { - PrintOutObject(value, addr, new List(path)); - } else { + } + else + { + if (!type.IsPrimitive) + { + this.PrintOutObject(value, addr, new List(path)); + } + else + { ImGui.Text($"{value}"); } } } - private ulong beginModule; - private ulong endModule; - - - private void PrintOutObject(object obj, ulong addr, List path, bool autoExpand = false) { - if (this.endModule == 0 && this.beginModule == 0) { - try { + private void PrintOutObject(object obj, ulong addr, List path, bool autoExpand = false) + { + if (this.endModule == 0 && this.beginModule == 0) + { + try + { var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) { - this.beginModule = (ulong) processModule.BaseAddress.ToInt64(); - this.endModule = (this.beginModule + (ulong) processModule.ModuleMemorySize); - } else { + if (processModule != null) + { + this.beginModule = (ulong)processModule.BaseAddress.ToInt64(); + this.endModule = this.beginModule + (ulong)processModule.ModuleMemorySize; + } + else + { this.endModule = 1; } - } catch { + } + catch + { this.endModule = 1; } } + ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); - if (autoExpand) { + if (autoExpand) + { ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); } - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) { - ImGui.PopStyleColor(); - foreach (var f in obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { - var fixedBuffer = (FixedBufferAttribute) f.GetCustomAttribute(typeof(FixedBufferAttribute)); - if (fixedBuffer != null) { + if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) + { + ImGui.PopStyleColor(); + foreach (var f in obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + { + var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + if (fixedBuffer != null) + { ImGui.Text($"fixed"); ImGui.SameLine(); ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); - } else { + } + else + { ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); } - + ImGui.SameLine(); ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); ImGui.SameLine(); - - PrintOutValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); + + this.PrintOutValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); } - foreach (var p in obj.GetType().GetProperties()) { + foreach (var p in obj.GetType().GetProperties()) + { ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); ImGui.SameLine(); ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); ImGui.SameLine(); - - PrintOutValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); + + this.PrintOutValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); } - + ImGui.TreePop(); - } else { + } + else + { ImGui.PopStyleColor(); } } diff --git a/Dalamud/NativeFunctions.cs b/Dalamud/NativeFunctions.cs index d6b94f751..d41d64985 100644 --- a/Dalamud/NativeFunctions.cs +++ b/Dalamud/NativeFunctions.cs @@ -1,67 +1,248 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; -#pragma warning disable SA1600 // Elements should be documented -#pragma warning disable SA1602 // Enumeration items should be documented - namespace Dalamud { - internal static class NativeFunctions + /// + /// Native user32 functions. + /// + internal static partial class NativeFunctions { + /// + /// FLASHW_* from winuser. + /// public enum FlashWindow : uint { /// /// Stop flashing. The system restores the window to its original state. /// - FLASHW_STOP = 0, + Stop = 0, /// - /// Flash the window caption + /// Flash the window caption. /// - FLASHW_CAPTION = 1, + Caption = 1, /// /// Flash the taskbar button. /// - FLASHW_TRAY = 2, + Tray = 2, /// /// Flash both the window caption and taskbar button. /// This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. /// - FLASHW_ALL = 3, + All = 3, /// /// Flash continuously, until the FLASHW_STOP flag is set. /// - FLASHW_TIMER = 4, + Timer = 4, /// /// Flash continuously until the window comes to the foreground. /// - FLASHW_TIMERNOFG = 12, + TimerNoFG = 12, } - [Flags] - public enum ErrorModes : uint + /// + /// MB_* from winuser. + /// + public enum MessageBoxType : uint { - SYSTEM_DEFAULT = 0x0, - SEM_FAILCRITICALERRORS = 0x0001, - SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, - SEM_NOGPFAULTERRORBOX = 0x0002, - SEM_NOOPENFILEERRORBOX = 0x8000, + // To indicate the buttons displayed in the message box, specify one of the following values. + + /// + /// The message box contains three push buttons: Abort, Retry, and Ignore. + /// + AbortRetryIgnore = 0x2, + + /// + /// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead + /// of MB_ABORTRETRYIGNORE. + /// + CancelTryContinue = 0x6, + + /// + /// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends + /// a WM_HELP message to the owner. + /// + Help = 0x4000, + + /// + /// The message box contains one push button: OK. This is the default. + /// + Ok = 0x0, + + /// + /// The message box contains two push buttons: OK and Cancel. + /// + OkCancel = 0x1, + + /// + /// The message box contains two push buttons: Retry and Cancel. + /// + RetryCancel = 0x5, + + /// + /// The message box contains two push buttons: Yes and No. + /// + YesNo = 0x4, + + /// + /// The message box contains three push buttons: Yes, No, and Cancel. + /// + YesNoCancel = 0x3, + + // To display an icon in the message box, specify one of the following values. + + /// + /// An exclamation-point icon appears in the message box. + /// + IconExclamation = 0x30, + + /// + /// An exclamation-point icon appears in the message box. + /// + IconWarning = 0x30, + + /// + /// An icon consisting of a lowercase letter i in a circle appears in the message box. + /// + IconInformation = 0x40, + + /// + /// An icon consisting of a lowercase letter i in a circle appears in the message box. + /// + IconAsterisk = 0x40, + + /// + /// A question-mark icon appears in the message box. + /// The question-mark message icon is no longer recommended because it does not clearly represent a specific type + /// of message and because the phrasing of a message as a question could apply to any message type. In addition, + /// users can confuse the message symbol question mark with Help information. Therefore, do not use this question + /// mark message symbol in your message boxes. The system continues to support its inclusion only for backward + /// compatibility. + /// + IconQuestion = 0x20, + + /// + /// A stop-sign icon appears in the message box. + /// + IconStop = 0x10, + + /// + /// A stop-sign icon appears in the message box. + /// + IconError = 0x10, + + /// + /// A stop-sign icon appears in the message box. + /// + IconHand = 0x10, + + // To indicate the default button, specify one of the following values. + + /// + /// The first button is the default button. + /// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified. + /// + DefButton1 = 0x0, + + /// + /// The second button is the default button. + /// + DefButton2 = 0x100, + + /// + /// The third button is the default button. + /// + DefButton3 = 0x200, + + /// + /// The fourth button is the default button. + /// + DefButton4 = 0x300, + + // To indicate the modality of the dialog box, specify one of the following values. + + /// + /// The user must respond to the message box before continuing work in the window identified by the hWnd parameter. + /// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy + /// of windows in the application, the user may be able to move to other windows within the thread. All child windows + /// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the + /// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified. + /// + ApplModal = 0x0, + + /// + /// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style. + /// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate + /// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with + /// windows other than those associated with hWnd. + /// + SystemModal = 0x1000, + + /// + /// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the + /// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle + /// available but still needs to prevent input to other windows in the calling thread without suspending other threads. + /// + TaskModal = 0x2000, + + // To specify other options, use one or more of the following values. + + /// + /// Same as desktop of the interactive window station. For more information, see Window Stations. If the current + /// input desktop is not the default desktop, MessageBox does not return until the user switches to the default + /// desktop. + /// + DefaultDesktopOnly = 0x20000, + + /// + /// The text is right-justified. + /// + Right = 0x80000, + + /// + /// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems. + /// + RtlReading = 0x100000, + + /// + /// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function + /// for the message box. + /// + SetForeground = 0x10000, + + /// + /// The message box is created with the WS_EX_TOPMOST window style. + /// + Topmost = 0x40000, + + /// + /// The caller is a service notifying the user of an event. The function displays a message box on the current active + /// desktop, even if there is no user logged on to the computer. + /// + ServiceNotification = 0x200000, } - /// Returns true if the current application has focus, false otherwise. - /// If the current application is focused. + /// + /// Returns true if the current application has focus, false otherwise. + /// + /// + /// If the current application is focused. + /// public static bool ApplicationIsActivated() { var activatedHandle = GetForegroundWindow(); if (activatedHandle == IntPtr.Zero) { - return false; // No window is currently activated + return false; // No window is currently activated } var procId = Process.GetCurrentProcess().Id; @@ -70,23 +251,218 @@ namespace Dalamud return activeProcId == procId; } + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindowex. + /// Flashes the specified window. It does not change the active state of the window. + /// + /// + /// A pointer to a FLASHWINFO structure. + /// + /// + /// The return value specifies the window's state before the call to the FlashWindowEx function. If the window caption + /// was drawn as active before the call, the return value is nonzero. Otherwise, the return value is zero. + /// + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FlashWindowEx(ref FlashWindowInfo pwfi); + + /// + /// Retrieves a handle to the foreground window (the window with which the user is currently working). The system assigns + /// a slightly higher priority to the thread that creates the foreground window than it does to other threads. + /// + /// + /// The return value is a handle to the foreground window. The foreground window can be NULL in certain circumstances, + /// such as when a window is losing activation. + /// [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern IntPtr GetForegroundWindow(); + /// + /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the + /// process that created the window. + /// + /// + /// A handle to the window. + /// + /// + /// A pointer to a variable that receives the process identifier. If this parameter is not NULL, GetWindowThreadProcessId + /// copies the identifier of the process to the variable; otherwise, it does not. + /// + /// + /// The return value is the identifier of the thread that created the window. + /// [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FlashWindowEx(ref FLASHWINFO pwfi); + /// + /// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, + /// such as status or error information. The message box returns an integer value that indicates which button the user + /// clicked. + /// + /// + /// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no + /// owner window. + /// + /// + /// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage + /// return and/or linefeed character between each line. + /// + /// + /// The dialog box title. If this parameter is NULL, the default title is Error. + /// + /// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups + /// of flags. + /// + /// + /// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or + /// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an + /// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK. + /// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function + /// succeeds, the return value is one of the ID* enum values. + /// + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern int MessageBox(IntPtr hWnd, string text, string caption, MessageBoxType type); + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-flashwinfo. + /// Contains the flash status for a window and the number of times the system should flash the window. + /// + [StructLayout(LayoutKind.Sequential)] + public struct FlashWindowInfo + { + /// + /// The size of the structure, in bytes. + /// + public uint cbSize; + + /// + /// A handle to the window to be flashed. The window can be either opened or minimized. + /// + public IntPtr hwnd; + + /// + /// The flash status. This parameter can be one or more of the FlashWindow enum values. + /// + public FlashWindow dwFlags; + + /// + /// The number of times to flash the window. + /// + public uint uCount; + + /// + /// The rate at which the window is to be flashed, in milliseconds. If dwTimeout is zero, the function uses the + /// default cursor blink rate. + /// + public uint dwTimeout; + } + } + + /// + /// Native kernel32 functions. + /// + internal static partial class NativeFunctions + { + /// + /// SEM_* from errhandlingapi. + /// + [Flags] + public enum ErrorModes : uint + { + /// + /// Use the system default, which is to display all error dialog boxes. + /// + SystemDefault = 0x0, + + /// + /// The system does not display the critical-error-handler message box. Instead, the system sends the error to the + /// calling process. Best practice is that all applications call the process-wide SetErrorMode function with a parameter + /// of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application. + /// + FailCriticalErrors = 0x0001, + + /// + /// The system automatically fixes memory alignment faults and makes them invisible to the application. It does + /// this for the calling process and any descendant processes. This feature is only supported by certain processor + /// architectures. For more information, see the Remarks section. After this value is set for a process, subsequent + /// attempts to clear the value are ignored. + /// + NoAlignmentFaultExcept = 0x0004, + + /// + /// The system does not display the Windows Error Reporting dialog. + /// + NoGpFaultErrorBox = 0x0002, + + /// + /// The OpenFile function does not display a message box when it fails to find a file. Instead, the error is returned + /// to the caller. This error mode overrides the OF_PROMPT flag. + /// + NoOpenFileErrorBox = 0x8000, + } + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugactiveprocess. + /// Enables a debugger to attach to an active process and debug it. + /// + /// + /// The identifier for the process to be debugged. The debugger is granted debugging access to the process as if it + /// created the process with the DEBUG_ONLY_THIS_PROCESS flag. For more information, see the Remarks section of this + /// topic. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool DebugActiveProcess(uint dwProcessId); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary. + /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference + /// count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer + /// valid. + /// + /// + /// A handle to the loaded library module. The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function + /// returns this handle. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended + /// error information, call the GetLastError function. + /// [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool FreeLibrary(IntPtr hModule); - [DllImport("kernel32.dll", CharSet = CharSet.Auto)] - public static extern IntPtr GetModuleHandle(string lpModuleName); - + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew. + /// Retrieves the fully qualified path for the file that contains the specified module. The module must have been loaded + /// by the current process. To locate the file for a module that was loaded by another process, use the GetModuleFileNameEx + /// function. + /// + /// + /// A handle to the loaded module whose path is being requested. If this parameter is NULL, GetModuleFileName retrieves + /// the path of the executable file of the current process. The GetModuleFileName function does not retrieve the path + /// for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag. For more information, see LoadLibraryEx. + /// + /// + /// A pointer to a buffer that receives the fully qualified path of the module. If the length of the path is less than + /// the size that the nSize parameter specifies, the function succeeds and the path is returned as a null-terminated + /// string. If the length of the path exceeds the size that the nSize parameter specifies, the function succeeds and + /// the string is truncated to nSize characters including the terminating null character. + /// + /// + /// The size of the lpFilename buffer, in TCHARs. + /// + /// + /// If the function succeeds, the return value is the length of the string that is copied to the buffer, in characters, + /// not including the terminating null character. If the buffer is too small to hold the module name, the string is + /// truncated to nSize characters including the terminating null character, the function returns nSize, and the function + /// sets the last error to ERROR_INSUFFICIENT_BUFFER. If nSize is zero, the return value is zero and the last error + /// code is ERROR_SUCCESS. If the function fails, the return value is 0 (zero). To get extended error information, call + /// GetLastError. + /// [DllImport("kernel32.dll", SetLastError = true)] [PreserveSig] public static extern uint GetModuleFileName( @@ -94,35 +470,152 @@ namespace Dalamud [Out] StringBuilder lpFilename, [In][MarshalAs(UnmanagedType.U4)] int nSize); + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. + /// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To + /// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function. + /// + /// + /// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default + /// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate + /// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure + /// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules + /// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns + /// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve + /// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return + /// value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr GetModuleHandle(string lpModuleName); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw. + /// Loads the specified module into the address space of the calling process. The specified module may cause other modules + /// to be loaded. For additional load options, use the LoadLibraryEx function. + /// + /// + /// The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). + /// The name specified is the file name of the module and is not related to the name stored in the library module itself, + /// as specified by the LIBRARY keyword in the module-definition (.def) file. If the string specifies a full path, the + /// function searches only that path for the module. If the string specifies a relative path or a module name without + /// a path, the function uses a standard search strategy to find the module; for more information, see the Remarks. + /// If the function cannot find the module, the function fails.When specifying a path, be sure to use backslashes (\), + /// not forward slashes(/). For more information about paths, see Naming a File or Directory. If the string specifies + /// a module name without a path and the file name extension is omitted, the function appends the default library extension + /// .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point + /// character (.) in the module name string. + /// + /// + /// If the function succeeds, the return value is a handle to the module. If the function fails, the return value is + /// NULL.To get extended error information, call GetLastError. + /// [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)] public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName); - [DllImport("kernel32.dll")] - public static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); + /// + /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process + /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can + /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. + /// + /// + /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. + /// + /// + /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the + /// system verifies that all data in the base address and memory of the specified size is accessible for read access, + /// and if it is not accessible the function fails. + /// + /// + /// A pointer to a buffer that receives the contents from the address space of the specified process. + /// + /// + /// The number of bytes to be read from the specified process. + /// + /// + /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead + /// is NULL, the parameter is ignored. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an + /// area of the process that is inaccessible. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + IntPtr lpBuffer, + int dwSize, + out IntPtr lpNumberOfBytesRead); - [DllImport("kernel32.dll")] + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode. + /// Controls whether the system will handle the specified types of serious errors or whether the process will handle + /// them. + /// + /// + /// The process error mode. This parameter can be one or more of the ErrorMode enum values. + /// + /// + /// The return value is the previous state of the error-mode bit flags. + /// + [DllImport("kernel32.dll", SetLastError = true)] public static extern ErrorModes SetErrorMode(ErrorModes uMode); - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool DebugActiveProcess(uint dwProcessId); + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter. + /// Enables an application to supersede the top-level exception handler of each thread of a process. After calling this + /// function, if an exception occurs in a process that is not being debugged, and the exception makes it to the unhandled + /// exception filter, that filter will call the exception filter function specified by the lpTopLevelExceptionFilter + /// parameter. + /// + /// + /// A pointer to a top-level exception filter function that will be called whenever the UnhandledExceptionFilter function + /// gets control, and the process is not being debugged. A value of NULL for this parameter specifies default handling + /// within UnhandledExceptionFilter. The filter function has syntax similar to that of UnhandledExceptionFilter: It + /// takes a single parameter of type LPEXCEPTION_POINTERS, has a WINAPI calling convention, and returns a value of type + /// LONG. The filter function should return one of the EXCEPTION_* enum values. + /// + /// + /// The SetUnhandledExceptionFilter function returns the address of the previous exception filter established with the + /// function. A NULL return value means that there is no current top-level exception handler. + /// + [DllImport("kernel32.dll")] + public static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); + } -#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter -#pragma warning disable SA1121 // Use built-in type alias - - [StructLayout(LayoutKind.Sequential)] - public struct FLASHWINFO - { - public UInt32 cbSize; - public IntPtr hwnd; - public FlashWindow dwFlags; - public UInt32 uCount; - public UInt32 dwTimeout; - } - -#pragma warning restore SA1121 // Use built-in type alias -#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter + /// + /// Native ws2_32 functions. + /// + internal static partial class NativeFunctions + { + /// + /// The setsockopt function sets a socket option. + /// + /// + /// A descriptor that identifies a socket. + /// + /// + /// The level at which the option is defined (for example, SOL_SOCKET). + /// + /// + /// The socket option for which the value is to be set (for example, SO_BROADCAST). The optname parameter must be a + /// socket option defined within the specified level, or behavior is undefined. + /// + /// + /// A pointer to the buffer in which the value for the requested option is specified. + /// + /// + /// The size, in bytes, of the buffer pointed to by the optval parameter. + /// + /// + /// If no error occurs, setsockopt returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error + /// code can be retrieved by calling WSAGetLastError. + /// + [DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi, EntryPoint = "setsockopt")] + public static extern int SetSockOpt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen); } } - -#pragma warning restore SA1600 // Elements should be documented -#pragma warning restore SA1602 // Enumeration items should be documented diff --git a/Dalamud/Plugin/PluginInstallerWindow.cs b/Dalamud/Plugin/PluginInstallerWindow.cs index b0e0fe3f8..6585fd7df 100644 --- a/Dalamud/Plugin/PluginInstallerWindow.cs +++ b/Dalamud/Plugin/PluginInstallerWindow.cs @@ -72,6 +72,9 @@ namespace Dalamud.Plugin LastUpdate, } + /// + /// Code to be executed when the window is opened. + /// public override void OnOpen() { base.OnOpen(); @@ -466,13 +469,13 @@ namespace Dalamud.Plugin Task.Run(() => this.dalamud.PluginRepository.InstallPlugin(pluginDefinition, true, false, isTestingAvailable)).ContinueWith(t => { - this.installStatus = - t.Result ? PluginInstallStatus.Success : PluginInstallStatus.Fail; - this.installStatus = - t.IsFaulted ? PluginInstallStatus.Fail : this.installStatus; + this.installStatus = + t.Result ? PluginInstallStatus.Success : PluginInstallStatus.Fail; + this.installStatus = + t.IsFaulted ? PluginInstallStatus.Fail : this.installStatus; - this.errorModalDrawing = this.installStatus == PluginInstallStatus.Fail; - this.errorModalOnNextFrame = this.installStatus == PluginInstallStatus.Fail; + this.errorModalDrawing = this.installStatus == PluginInstallStatus.Fail; + this.errorModalOnNextFrame = this.installStatus == PluginInstallStatus.Fail; }); } } diff --git a/Dalamud/Plugin/PluginManager.cs b/Dalamud/Plugin/PluginManager.cs index 4d625d86a..f45b0fc91 100644 --- a/Dalamud/Plugin/PluginManager.cs +++ b/Dalamud/Plugin/PluginManager.cs @@ -31,6 +31,8 @@ namespace Dalamud.Plugin private readonly List bannedPlugins; + private IEnumerable<(FileInfo DllFile, PluginDefinition Definition, bool IsRaw)> deferredPlugins; + /// /// Initializes a new instance of the class. /// @@ -43,9 +45,7 @@ namespace Dalamud.Plugin this.pluginDirectory = pluginDirectory; this.devPluginDirectory = devPluginDirectory; - this.Plugins = - new List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, - bool IsRaw)>(); + this.Plugins = new List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, bool IsRaw)>(); this.IpcSubscriptions = new List<(string SourcePluginName, string SubPluginName, Action SubAction)>(); this.pluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(dalamud.StartInfo.ConfigurationPath), "pluginConfigs")); @@ -109,20 +109,18 @@ namespace Dalamud.Plugin this.Plugins.Clear(); } - private IEnumerable<(FileInfo dllFile, PluginDefinition definition, bool isRaw)> deferredPlugins; - /// /// Load plugins that need to be loaded synchronously and prepare plugins that can be loaded asynchronously. /// public void LoadSynchronousPlugins() { - var loadDirectories = new List<(DirectoryInfo dirInfo, bool isRaw)> + var loadDirectories = new List<(DirectoryInfo DirInfo, bool IsRaw)> { (new DirectoryInfo(this.pluginDirectory), false), (new DirectoryInfo(this.devPluginDirectory), true), }; - var pluginDefs = new List<(FileInfo dllFile, PluginDefinition definition, bool isRaw)>(); + var pluginDefs = new List<(FileInfo DllFile, PluginDefinition Definition, bool IsRaw)>(); foreach (var (dirInfo, isRaw) in loadDirectories) { if (!dirInfo.Exists) continue; @@ -144,15 +142,15 @@ namespace Dalamud.Plugin pluginDefs.Sort( (info1, info2) => { - var prio1 = info1.definition?.LoadPriority ?? 0; - var prio2 = info2.definition?.LoadPriority ?? 0; + var prio1 = info1.Definition?.LoadPriority ?? 0; + var prio2 = info2.Definition?.LoadPriority ?? 0; return prio2.CompareTo(prio1); }); - this.deferredPlugins = pluginDefs.Where(x => x.definition == null || x.definition.LoadPriority <= 0); + this.deferredPlugins = pluginDefs.Where(x => x.Definition == null || x.Definition.LoadPriority <= 0); // Pass preloaded definitions for "synchronous load" plugins to LoadPluginFromAssembly, because we already loaded them anyways - foreach (var (dllFile, definition, isRaw) in pluginDefs.Where(x => x.definition?.LoadPriority > 0)) + foreach (var (dllFile, definition, isRaw) in pluginDefs.Where(x => x.Definition?.LoadPriority > 0)) { try { @@ -172,6 +170,9 @@ namespace Dalamud.Plugin } } + /// + /// Load plugins that have been explicitly deferred. + /// public void LoadDeferredPlugins() { if (this.deferredPlugins == null) @@ -236,7 +237,7 @@ namespace Dalamud.Plugin /// The associated with the main assembly of this plugin. /// Whether or not the plugin is a dev plugin. /// The reason this plugin was loaded. - /// The already loaded definition, if available + /// The already loaded definition, if available. /// Whether or not the plugin was loaded successfully. public bool LoadPluginFromAssembly(FileInfo dllFile, bool isRaw, PluginLoadReason reason, PluginDefinition pluginDef = null) { diff --git a/Dalamud/Plugin/PluginRepository.cs b/Dalamud/Plugin/PluginRepository.cs index 2340b61ae..78ec9796a 100644 --- a/Dalamud/Plugin/PluginRepository.cs +++ b/Dalamud/Plugin/PluginRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; @@ -9,6 +8,7 @@ using System.Net; using System.Reflection; using System.Text; using System.Threading.Tasks; + using CheapLoc; using Dalamud.Game.Text; using Newtonsoft.Json; @@ -16,34 +16,80 @@ using Serilog; namespace Dalamud.Plugin { - internal class PluginRepository { - private string PluginMasterUrl => "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/master/pluginmaster.json"; + /// + /// This class represents a single plugin repository. + /// + internal class PluginRepository + { + private const string PluginMasterUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/master/pluginmaster.json"; private readonly Dalamud dalamud; private string pluginDirectory; - public ReadOnlyCollection PluginMaster; - public enum InitializationState { - Unknown, - InProgress, - Success, - Fail, - FailThirdRepo - } - - public InitializationState State { get; private set; } - - public PluginRepository(Dalamud dalamud, string pluginDirectory, string gameVersion) { + /// + /// Initializes a new instance of the class. + /// + /// The Dalamud instance. + /// The plugin directory path. + /// The current game version. + public PluginRepository(Dalamud dalamud, string pluginDirectory, string gameVersion) + { this.dalamud = dalamud; this.pluginDirectory = pluginDirectory; - ReloadPluginMasterAsync(); + this.ReloadPluginMasterAsync(); } - public void ReloadPluginMasterAsync() { - State = InitializationState.InProgress; + /// + /// Values representing plugin initialization state. + /// + public enum InitializationState + { + /// + /// State is unknown. + /// + Unknown, - Task.Run(() => { + /// + /// State is in progress. + /// + InProgress, + + /// + /// State is successful. + /// + Success, + + /// + /// State is failure. + /// + Fail, + + /// + /// State is failure, for a 3rd party repo plugin. + /// + FailThirdRepo, + } + + /// + /// Gets the plugin master list of available plugins. + /// + public ReadOnlyCollection PluginMaster { get; private set; } + + /// + /// Gets the initialization state of the plugin repository. + /// + public InitializationState State { get; private set; } + + /// + /// Reload the plugin master asynchronously in a task. + /// + public void ReloadPluginMasterAsync() + { + this.State = InitializationState.InProgress; + + Task.Run(() => + { this.PluginMaster = null; var allPlugins = new List(); @@ -51,18 +97,21 @@ namespace Dalamud.Plugin var repos = this.dalamud.Configuration.ThirdRepoList.Where(x => x.IsEnabled).Select(x => x.Url) .Prepend(PluginMasterUrl).ToArray(); - try { + try + { using var client = new WebClient(); var repoNumber = 0; - foreach (var repo in repos) { + foreach (var repo in repos) + { Log.Information("[PLUGINR] Fetching repo: {0}", repo); - + var data = client.DownloadString(repo); var unsortedPluginMaster = JsonConvert.DeserializeObject>(data); - foreach (var pluginDefinition in unsortedPluginMaster) { + foreach (var pluginDefinition in unsortedPluginMaster) + { pluginDefinition.RepoNumber = repoNumber; } @@ -72,21 +121,33 @@ namespace Dalamud.Plugin } this.PluginMaster = allPlugins.AsReadOnly(); - State = InitializationState.Success; + this.State = InitializationState.Success; } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, "Could not download PluginMaster"); - State = repos.Length > 1 ? InitializationState.FailThirdRepo : InitializationState.Fail; + this.State = repos.Length > 1 ? InitializationState.FailThirdRepo : InitializationState.Fail; } - }).ContinueWith(t => { + }).ContinueWith(t => + { if (t.IsFaulted) - State = InitializationState.Fail; + this.State = InitializationState.Fail; }); } - public bool InstallPlugin(PluginDefinition definition, bool enableAfterInstall = true, bool isUpdate = false, bool fromTesting = false) { - try { + /// + /// Install a plugin. + /// + /// The plugin definition. + /// Whether the plugin should be immediately enabled. + /// Whether this install is an update. + /// Whether this install is flagged as testing. + /// Success or failure. + public bool InstallPlugin(PluginDefinition definition, bool enableAfterInstall = true, bool isUpdate = false, bool fromTesting = false) + { + try + { using var client = new WebClient(); var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory, definition.InternalName, fromTesting ? definition.TestingAssemblyVersion : definition.AssemblyVersion)); @@ -95,22 +156,27 @@ namespace Dalamud.Plugin var testingFile = new FileInfo(Path.Combine(outputDir.FullName, ".testing")); var wasDisabled = disabledFile.Exists; - if (dllFile.Exists && enableAfterInstall) { + if (dllFile.Exists && enableAfterInstall) + { if (disabledFile.Exists) disabledFile.Delete(); return this.dalamud.PluginManager.LoadPluginFromAssembly(dllFile, false, PluginLoadReason.Installer); } - if (dllFile.Exists && !enableAfterInstall) { + if (dllFile.Exists && !enableAfterInstall) + { return true; } - try { + try + { if (outputDir.Exists) outputDir.Delete(true); outputDir.Create(); - } catch { + } + catch + { // ignored, since the plugin may be loaded already } @@ -118,7 +184,8 @@ namespace Dalamud.Plugin var doTestingDownload = false; if ((Version.TryParse(definition.TestingAssemblyVersion, out var testingAssemblyVer) || definition.IsTestingExclusive) - && fromTesting) { + && fromTesting) + { doTestingDownload = testingAssemblyVer > Version.Parse(definition.AssemblyVersion) || definition.IsTestingExclusive; } @@ -136,81 +203,102 @@ namespace Dalamud.Plugin ZipFile.ExtractToDirectory(path, outputDir.FullName); - if (wasDisabled || !enableAfterInstall) { + if (wasDisabled || !enableAfterInstall) + { disabledFile.Create().Close(); return true; } - if (doTestingDownload) { + if (doTestingDownload) + { testingFile.Create().Close(); - } else { + } + else + { if (testingFile.Exists) testingFile.Delete(); } return this.dalamud.PluginManager.LoadPluginFromAssembly(dllFile, false, PluginLoadReason.Installer); } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, "Plugin download failed hard."); - if (ex is ReflectionTypeLoadException typeLoadException) { - foreach (var exception in typeLoadException.LoaderExceptions) { + if (ex is ReflectionTypeLoadException typeLoadException) + { + foreach (var exception in typeLoadException.LoaderExceptions) + { Log.Error(exception, "LoaderException:"); } } + return false; } } - internal class PluginUpdateStatus { - public string InternalName { get; set; } - public string Name { get; set; } - public string Version { get; set; } - public bool WasUpdated { get; set; } - } - - public (bool Success, List UpdatedPlugins) UpdatePlugins(bool dryRun = false) { + /// + /// Update all plugins. + /// + /// Perform a dry run of the update and skip the actual installation. + /// A tuple of whether the update was successful and the list of updated plugins. + public (bool Success, List UpdatedPlugins) UpdatePlugins(bool dryRun = false) + { Log.Information("Starting plugin update... dry:{0}", dryRun); var updatedList = new List(); var hasError = false; - try { + try + { var pluginsDirectory = new DirectoryInfo(this.pluginDirectory); - foreach (var installed in pluginsDirectory.GetDirectories()) { - try { + foreach (var installed in pluginsDirectory.GetDirectories()) + { + try + { var versions = installed.GetDirectories(); - if (versions.Length == 0) { + if (versions.Length == 0) + { Log.Information("Has no versions: {0}", installed.FullName); continue; } - var sortedVersions = versions.OrderBy(dirInfo => { - var success = Version.TryParse(dirInfo.Name, out Version version); - if (!success) { Log.Debug("Unparseable version: {0}", dirInfo.Name); } + var sortedVersions = versions.OrderBy(dirInfo => + { + var success = Version.TryParse(dirInfo.Name, out var version); + if (!success) + { + Log.Debug("Unparseable version: {0}", dirInfo.Name); + } + return version; }); var latest = sortedVersions.Last(); var isEnabled = !File.Exists(Path.Combine(latest.FullName, ".disabled")); - if (!isEnabled && File.Exists(Path.Combine(latest.FullName, ".testing"))) { + if (!isEnabled && File.Exists(Path.Combine(latest.FullName, ".testing"))) + { // In case testing is installed, but stable is enabled - foreach (var version in versions) { - if (!File.Exists(Path.Combine(version.FullName, ".disabled"))) { + foreach (var version in versions) + { + if (!File.Exists(Path.Combine(version.FullName, ".disabled"))) + { isEnabled = true; break; } } } - if (!isEnabled) { + if (!isEnabled) + { Log.Verbose("Is disabled: {0}", installed.FullName); continue; } var localInfoFile = new FileInfo(Path.Combine(latest.FullName, $"{installed.Name}.json")); - if (!localInfoFile.Exists) { + if (!localInfoFile.Exists) + { Log.Information("Has no definition: {0}", localInfoFile.FullName); continue; } @@ -220,32 +308,37 @@ namespace Dalamud.Plugin var remoteInfo = this.PluginMaster.FirstOrDefault(x => x.InternalName == info.InternalName); - if (remoteInfo == null) { + if (remoteInfo == null) + { Log.Information("Is not in pluginmaster: {0}", info.Name); continue; } - if (remoteInfo.DalamudApiLevel < PluginManager.DalamudApiLevel) { + if (remoteInfo.DalamudApiLevel < PluginManager.DalamudApiLevel) + { Log.Information("Has not applicable API level: {0}", info.Name); continue; } - Version.TryParse(remoteInfo.AssemblyVersion, out Version remoteAssemblyVer); - Version.TryParse(info.AssemblyVersion, out Version localAssemblyVer); + Version.TryParse(remoteInfo.AssemblyVersion, out var remoteAssemblyVer); + Version.TryParse(info.AssemblyVersion, out var localAssemblyVer); var testingAvailable = false; - if (!string.IsNullOrEmpty(remoteInfo.TestingAssemblyVersion)) { + if (!string.IsNullOrEmpty(remoteInfo.TestingAssemblyVersion)) + { Version.TryParse(remoteInfo.TestingAssemblyVersion, out var testingAssemblyVer); testingAvailable = testingAssemblyVer > localAssemblyVer && this.dalamud.Configuration.DoPluginTest; } - - if (remoteAssemblyVer > localAssemblyVer || testingAvailable) { + + if (remoteAssemblyVer > localAssemblyVer || testingAvailable) + { Log.Information("Eligible for update: {0}", remoteInfo.InternalName); // DisablePlugin() below immediately creates a .disabled file anyway, but will fail // with an exception if we try to do it twice in row like this - if (!dryRun) { + if (!dryRun) + { var wasLoaded = this.dalamud.PluginManager.Plugins.Where(x => x.Definition != null).Any( x => x.Definition.InternalName == info.InternalName); @@ -253,58 +346,75 @@ namespace Dalamud.Plugin Log.Verbose("isEnabled: {0} / wasLoaded: {1}", isEnabled, wasLoaded); // Try to disable plugin if it is loaded - if (wasLoaded) { - try { + if (wasLoaded) + { + try + { this.dalamud.PluginManager.DisablePlugin(info); } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, "Plugin disable failed"); - //hasError = true; + // hasError = true; } } - try { + try + { // Just to be safe - foreach (var sortedVersion in sortedVersions) { + foreach (var sortedVersion in sortedVersions) + { var disabledFile = new FileInfo(Path.Combine(sortedVersion.FullName, ".disabled")); if (!disabledFile.Exists) disabledFile.Create().Close(); } - } catch (Exception ex) { + } + catch (Exception ex) + { Log.Error(ex, "Plugin disable old versions failed"); } - var installSuccess = InstallPlugin(remoteInfo, isEnabled, true, testingAvailable); + var installSuccess = this.InstallPlugin(remoteInfo, isEnabled, true, testingAvailable); - if (!installSuccess) { + if (!installSuccess) + { Log.Error("InstallPlugin failed."); hasError = true; } - updatedList.Add(new PluginUpdateStatus { + updatedList.Add(new PluginUpdateStatus + { InternalName = remoteInfo.InternalName, Name = remoteInfo.Name, Version = testingAvailable ? remoteInfo.TestingAssemblyVersion : remoteInfo.AssemblyVersion, - WasUpdated = installSuccess - }); - } else { - updatedList.Add(new PluginUpdateStatus { - InternalName = remoteInfo.InternalName, - Name = remoteInfo.Name, - Version = testingAvailable ? remoteInfo.TestingAssemblyVersion : remoteInfo.AssemblyVersion, - WasUpdated = true + WasUpdated = installSuccess, }); } - } else { + else + { + updatedList.Add(new PluginUpdateStatus + { + InternalName = remoteInfo.InternalName, + Name = remoteInfo.Name, + Version = testingAvailable ? remoteInfo.TestingAssemblyVersion : remoteInfo.AssemblyVersion, + WasUpdated = true, + }); + } + } + else + { Log.Information("Up to date: {0}", remoteInfo.InternalName); } - } catch (Exception ex) { + } + catch (Exception ex) + { Log.Error(ex, "Could not update plugin: {0}", installed.FullName); } } } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, "Plugin update failed."); hasError = true; } @@ -314,31 +424,54 @@ namespace Dalamud.Plugin return (!hasError, updatedList); } - public void PrintUpdatedPlugins(List updatedPlugins, string header) { - if (updatedPlugins != null && updatedPlugins.Any()) { + /// + /// Print to chat any plugin updates and whether they were successful. + /// + /// The list of updated plugins. + /// The header text to send to chat prior to any update info. + public void PrintUpdatedPlugins(List updatedPlugins, string header) + { + if (updatedPlugins != null && updatedPlugins.Any()) + { this.dalamud.Framework.Gui.Chat.Print(header); - foreach (var plugin in updatedPlugins) { - if (plugin.WasUpdated) { + foreach (var plugin in updatedPlugins) + { + if (plugin.WasUpdated) + { this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudPluginUpdateSuccessful", " 》 {0} updated to v{1}."), plugin.Name, plugin.Version)); - } else { - this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry { + } + else + { + this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry + { MessageBytes = Encoding.UTF8.GetBytes(string.Format(Loc.Localize("DalamudPluginUpdateFailed", " 》 {0} update to v{1} failed."), plugin.Name, plugin.Version)), - Type = XivChatType.Urgent + Type = XivChatType.Urgent, }); } } } } - public void CleanupPlugins() { - try { + /// + /// Cleanup disabled plugins. + /// + public void CleanupPlugins() + { + try + { var pluginsDirectory = new DirectoryInfo(this.pluginDirectory); - foreach (var installed in pluginsDirectory.GetDirectories()) { + foreach (var installed in pluginsDirectory.GetDirectories()) + { var versions = installed.GetDirectories(); - var sortedVersions = versions.OrderBy(dirInfo => { - var success = Version.TryParse(dirInfo.Name, out Version version); - if (!success) { Log.Debug("Unparseable version: {0}", dirInfo.Name); } + var sortedVersions = versions.OrderBy(dirInfo => + { + var success = Version.TryParse(dirInfo.Name, out var version); + if (!success) + { + Log.Debug("Unparseable version: {0}", dirInfo.Name); + } + return version; }).ToArray(); @@ -348,15 +481,17 @@ namespace Dalamud.Plugin { var disabledFile = new FileInfo(Path.Combine(version.FullName, ".disabled")); var definition = JsonConvert.DeserializeObject( - File.ReadAllText(Path.Combine(version.FullName, - version.Parent.Name + ".json"))); + File.ReadAllText(Path.Combine(version.FullName, version.Parent.Name + ".json"))); - if (disabledFile.Exists) { + if (disabledFile.Exists) + { Log.Information("[PLUGINR] Disabled: cleaning up {0} at {1}", installed.Name, version.FullName); - try { + try + { version.Delete(true); } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, $"[PLUGINR] Could not clean up {disabledFile.FullName}"); } } @@ -364,10 +499,12 @@ namespace Dalamud.Plugin if (definition.DalamudApiLevel < PluginManager.DalamudApiLevel - 1) { Log.Information("[PLUGINR] Lower API: cleaning up {0} at {1}", installed.Name, version.FullName); - try { + try + { version.Delete(true); } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, $"[PLUGINR] Could not clean up {disabledFile.FullName}"); } } @@ -393,9 +530,36 @@ namespace Dalamud.Plugin } } } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, "[PLUGINR] Plugin cleanup failed."); } } + + /// + /// Plugin update status. + /// + internal class PluginUpdateStatus + { + /// + /// Gets or sets the plugin internal name. + /// + public string InternalName { get; set; } + + /// + /// Gets or sets the plugin name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the plugin version. + /// + public string Version { get; set; } + + /// + /// Gets or sets a value indicating whether the plugin was updated. + /// + public bool WasUpdated { get; set; } + } } } diff --git a/Dalamud/Util.cs b/Dalamud/Util.cs index e9a269db0..e851f87a0 100644 --- a/Dalamud/Util.cs +++ b/Dalamud/Util.cs @@ -2,7 +2,6 @@ using System; using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.Text; using Dalamud.Game; @@ -154,12 +153,16 @@ namespace Dalamud } } - [DllImport("user32.dll", SetLastError = true, CharSet= CharSet.Auto)] - public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); - + /// + /// Display an error MessageBox and exit the current process. + /// + /// MessageBox body. + /// MessageBox caption (title). public static void Fatal(string message, string caption) { - MessageBox(Process.GetCurrentProcess().MainWindowHandle, message, caption, 0); + var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError; + + NativeFunctions.MessageBox(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); Environment.Exit(-1); } }