diff --git a/Dalamud/ClientLanguage.cs b/Dalamud/ClientLanguage.cs
index ddd69576d..4e04d4a54 100644
--- a/Dalamud/ClientLanguage.cs
+++ b/Dalamud/ClientLanguage.cs
@@ -1,28 +1,27 @@
-namespace Dalamud
+namespace Dalamud;
+
+///
+/// Enum describing the language the game loads in.
+///
+public enum ClientLanguage
{
///
- /// Enum describing the language the game loads in.
+ /// Indicating a Japanese game client.
///
- public enum ClientLanguage
- {
- ///
- /// Indicating a Japanese game client.
- ///
- Japanese,
+ Japanese,
- ///
- /// Indicating an English game client.
- ///
- English,
+ ///
+ /// Indicating an English game client.
+ ///
+ English,
- ///
- /// Indicating a German game client.
- ///
- German,
+ ///
+ /// Indicating a German game client.
+ ///
+ German,
- ///
- /// Indicating a French game client.
- ///
- French,
- }
+ ///
+ /// Indicating a French game client.
+ ///
+ French,
}
diff --git a/Dalamud/ClientLanguageExtensions.cs b/Dalamud/ClientLanguageExtensions.cs
index abfba3ad5..e19ca1eb1 100644
--- a/Dalamud/ClientLanguageExtensions.cs
+++ b/Dalamud/ClientLanguageExtensions.cs
@@ -1,27 +1,26 @@
using System;
-namespace Dalamud
+namespace Dalamud;
+
+///
+/// Extension methods for the class.
+///
+public static class ClientLanguageExtensions
{
///
- /// Extension methods for the class.
+ /// Converts a Dalamud ClientLanguage to the corresponding Lumina variant.
///
- public static class ClientLanguageExtensions
+ /// Language to convert.
+ /// Converted language.
+ public static Lumina.Data.Language ToLumina(this ClientLanguage language)
{
- ///
- /// Converts a Dalamud ClientLanguage to the corresponding Lumina variant.
- ///
- /// Language to convert.
- /// Converted language.
- public static Lumina.Data.Language ToLumina(this ClientLanguage language)
+ return language switch
{
- return 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(language)),
- };
- }
+ 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(language)),
+ };
}
}
diff --git a/Dalamud/Configuration/IPluginConfiguration.cs b/Dalamud/Configuration/IPluginConfiguration.cs
index 884e38871..dcc93d8d7 100644
--- a/Dalamud/Configuration/IPluginConfiguration.cs
+++ b/Dalamud/Configuration/IPluginConfiguration.cs
@@ -1,13 +1,12 @@
-namespace Dalamud.Configuration
+namespace Dalamud.Configuration;
+
+///
+/// Configuration to store settings for a dalamud plugin.
+///
+public interface IPluginConfiguration
{
///
- /// Configuration to store settings for a dalamud plugin.
+ /// Gets or sets configuration version.
///
- public interface IPluginConfiguration
- {
- ///
- /// Gets or sets configuration version.
- ///
- int Version { get; set; }
- }
+ int Version { get; set; }
}
diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index 9ec6712aa..d4efde61c 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -10,368 +10,367 @@ using Newtonsoft.Json;
using Serilog;
using Serilog.Events;
-namespace Dalamud.Configuration.Internal
+namespace Dalamud.Configuration.Internal;
+
+///
+/// Class containing Dalamud settings.
+///
+[Serializable]
+internal sealed class DalamudConfiguration : IServiceType
{
- ///
- /// Class containing Dalamud settings.
- ///
- [Serializable]
- internal sealed class DalamudConfiguration : IServiceType
+ private static readonly JsonSerializerSettings SerializerSettings = new()
{
- private static readonly JsonSerializerSettings SerializerSettings = new()
+ TypeNameHandling = TypeNameHandling.All,
+ TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
+ Formatting = Formatting.Indented,
+ };
+
+ [JsonIgnore]
+ private string configPath;
+
+ ///
+ /// Delegate for the event that occurs when the dalamud configuration is saved.
+ ///
+ /// The current dalamud configuration.
+ public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
+
+ ///
+ /// Event that occurs when dalamud configuration is saved.
+ ///
+ public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
+
+ ///
+ /// Gets or sets a list of muted works.
+ ///
+ public List BadWords { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
+ ///
+ public bool DutyFinderTaskbarFlash { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
+ ///
+ public bool DutyFinderChatMessage { get; set; } = true;
+
+ ///
+ /// Gets or sets the language code to load Dalamud localization with.
+ ///
+ public string LanguageOverride { get; set; } = null;
+
+ ///
+ /// Gets or sets the last loaded Dalamud version.
+ ///
+ public string LastVersion { get; set; } = null;
+
+ ///
+ /// Gets or sets the last loaded Dalamud version.
+ ///
+ public string LastChangelogMajorMinor { get; set; } = null;
+
+ ///
+ /// Gets or sets the chat type used by default for plugin messages.
+ ///
+ public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
+
+ ///
+ /// Gets or sets a value indicating whether or not plugin testing builds should be shown.
+ ///
+ public bool DoPluginTest { get; set; } = false;
+
+ ///
+ /// Gets or sets a key to opt into Dalamud staging builds.
+ ///
+ public string? DalamudBetaKey { get; set; } = null;
+
+ ///
+ /// Gets or sets a list of custom repos.
+ ///
+ public List ThirdRepoList { get; set; } = new();
+
+ ///
+ /// Gets or sets a list of hidden plugins.
+ ///
+ public List HiddenPluginInternalName { get; set; } = new();
+
+ ///
+ /// Gets or sets a list of seen plugins.
+ ///
+ public List SeenPluginInternalName { get; set; } = new();
+
+ ///
+ /// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
+ /// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder.
+ /// However by specifiying this value manually, you can add arbitrary files outside the normal
+ /// file paths.
+ ///
+ public Dictionary DevPluginSettings { get; set; } = new();
+
+ ///
+ /// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
+ /// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
+ /// injected Dalamud instance.
+ ///
+ public List DevPluginLoadLocations { get; set; } = new();
+
+ ///
+ /// Gets or sets the global UI scale.
+ ///
+ public float GlobalUiScale { get; set; } = 1.0f;
+
+ ///
+ /// Gets or sets a value indicating whether to use AXIS fonts from the game.
+ ///
+ public bool UseAxisFontsFromGame { get; set; } = false;
+
+ ///
+ /// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness.
+ ///
+ /// Before gamma is applied...
+ /// * ...TTF fonts loaded with stb or FreeType are in linear space.
+ /// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4.
+ ///
+ public float FontGammaLevel { get; set; } = 1.4f;
+
+ ///
+ /// Gets or sets a value indicating whether or not plugin UI should be hidden.
+ ///
+ public bool ToggleUiHide { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
+ ///
+ public bool ToggleUiHideDuringCutscenes { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
+ ///
+ public bool ToggleUiHideDuringGpose { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
+ ///
+ public bool PrintPluginsWelcomeMsg { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not plugins should be auto-updated.
+ ///
+ public bool AutoUpdatePlugins { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
+ ///
+ public bool DoButtonsSystemMenu { get; set; } = true;
+
+ ///
+ /// Gets or sets the default Dalamud debug log level on startup.
+ ///
+ public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
+
+ ///
+ /// Gets or sets a value indicating whether to write to log files synchronously.
+ ///
+ public bool LogSynchronously { get; set; } = false;
+
+ ///
+ /// Gets or sets a value indicating whether or not the debug log should scroll automatically.
+ ///
+ public bool LogAutoScroll { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not the debug log should open at startup.
+ ///
+ public bool LogOpenAtStartup { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether or not the dev bar should open at startup.
+ ///
+ public bool DevBarOpenAtStartup { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
+ ///
+ public bool AssertsEnabledAtStartup { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
+ ///
+ public bool IsDocking { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether viewports should always be disabled.
+ ///
+ public bool IsDisableViewport { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
+ ///
+ public bool IsGamepadNavigationEnabled { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not focus management is enabled.
+ ///
+ public bool IsFocusManagementEnabled { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
+ ///
+ public bool IsAntiAntiDebugEnabled { get; set; } = false;
+
+ ///
+ /// Gets or sets a value indicating whether to resume game main thread after plugins load.
+ ///
+ public bool IsResumeGameAfterPluginLoad { get; set; } = false;
+
+ ///
+ /// Gets or sets the kind of beta to download when matches the server value.
+ ///
+ public string DalamudBetaKind { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
+ /// It is reset immediately when read.
+ ///
+ public bool PluginSafeMode { get; set; }
+
+ ///
+ /// Gets or sets a value indicating the wait time between plugin unload and plugin assembly unload.
+ /// Uses default value that may change between versions if set to null.
+ ///
+ public int? PluginWaitBeforeFree { get; set; }
+
+ ///
+ /// Gets or sets a list of saved styles.
+ ///
+ [JsonProperty("SavedStyles")]
+ public List? SavedStylesOld { get; set; }
+
+ ///
+ /// Gets or sets a list of saved styles.
+ ///
+ [JsonProperty("SavedStylesVersioned")]
+ public List? SavedStyles { get; set; }
+
+ ///
+ /// Gets or sets the name of the currently chosen style.
+ ///
+ public string ChosenStyle { get; set; } = "Dalamud Standard";
+
+ ///
+ /// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled.
+ ///
+ public bool DisableRmtFiltering { get; set; }
+
+ ///
+ /// Gets or sets the order of DTR elements, by title.
+ ///
+ public List? DtrOrder { get; set; }
+
+ ///
+ /// Gets or sets the list of ignored DTR elements, by title.
+ ///
+ public List? DtrIgnore { get; set; }
+
+ ///
+ /// Gets or sets the spacing used for DTR entries.
+ ///
+ public int DtrSpacing { get; set; } = 10;
+
+ ///
+ /// Gets or sets a value indicating whether to swap the
+ /// direction in which elements are drawn in the DTR.
+ /// False indicates that elements will be drawn from the end of
+ /// the left side of the Server Info bar, and continue leftwards.
+ /// True indicates the opposite.
+ ///
+ public bool DtrSwapDirection { get; set; } = false;
+
+ ///
+ /// Gets or sets a value indicating whether the title screen menu is shown.
+ ///
+ public bool ShowTsm { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether or not market board data should be uploaded.
+ ///
+ public bool IsMbCollect { get; set; } = true;
+
+ ///
+ /// Gets the ISO 639-1 two-letter code for the language of the effective Dalamud display language.
+ ///
+ public string EffectiveLanguage
+ {
+ get
{
- TypeNameHandling = TypeNameHandling.All,
- TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
- Formatting = Formatting.Indented,
- };
-
- [JsonIgnore]
- private string configPath;
-
- ///
- /// Delegate for the event that occurs when the dalamud configuration is saved.
- ///
- /// The current dalamud configuration.
- public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
-
- ///
- /// Event that occurs when dalamud configuration is saved.
- ///
- public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
-
- ///
- /// Gets or sets a list of muted works.
- ///
- public List BadWords { get; set; }
-
- ///
- /// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
- ///
- public bool DutyFinderTaskbarFlash { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
- ///
- public bool DutyFinderChatMessage { get; set; } = true;
-
- ///
- /// Gets or sets the language code to load Dalamud localization with.
- ///
- public string LanguageOverride { get; set; } = null;
-
- ///
- /// Gets or sets the last loaded Dalamud version.
- ///
- public string LastVersion { get; set; } = null;
-
- ///
- /// Gets or sets the last loaded Dalamud version.
- ///
- public string LastChangelogMajorMinor { get; set; } = null;
-
- ///
- /// Gets or sets the chat type used by default for plugin messages.
- ///
- public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
-
- ///
- /// Gets or sets a value indicating whether or not plugin testing builds should be shown.
- ///
- public bool DoPluginTest { get; set; } = false;
-
- ///
- /// Gets or sets a key to opt into Dalamud staging builds.
- ///
- public string? DalamudBetaKey { get; set; } = null;
-
- ///
- /// Gets or sets a list of custom repos.
- ///
- public List ThirdRepoList { get; set; } = new();
-
- ///
- /// Gets or sets a list of hidden plugins.
- ///
- public List HiddenPluginInternalName { get; set; } = new();
-
- ///
- /// Gets or sets a list of seen plugins.
- ///
- public List SeenPluginInternalName { get; set; } = new();
-
- ///
- /// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
- /// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder.
- /// However by specifiying this value manually, you can add arbitrary files outside the normal
- /// file paths.
- ///
- public Dictionary DevPluginSettings { get; set; } = new();
-
- ///
- /// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
- /// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
- /// injected Dalamud instance.
- ///
- public List DevPluginLoadLocations { get; set; } = new();
-
- ///
- /// Gets or sets the global UI scale.
- ///
- public float GlobalUiScale { get; set; } = 1.0f;
-
- ///
- /// Gets or sets a value indicating whether to use AXIS fonts from the game.
- ///
- public bool UseAxisFontsFromGame { get; set; } = false;
-
- ///
- /// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness.
- ///
- /// Before gamma is applied...
- /// * ...TTF fonts loaded with stb or FreeType are in linear space.
- /// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4.
- ///
- public float FontGammaLevel { get; set; } = 1.4f;
-
- ///
- /// Gets or sets a value indicating whether or not plugin UI should be hidden.
- ///
- public bool ToggleUiHide { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
- ///
- public bool ToggleUiHideDuringCutscenes { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
- ///
- public bool ToggleUiHideDuringGpose { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
- ///
- public bool PrintPluginsWelcomeMsg { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not plugins should be auto-updated.
- ///
- public bool AutoUpdatePlugins { get; set; }
-
- ///
- /// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
- ///
- public bool DoButtonsSystemMenu { get; set; } = true;
-
- ///
- /// Gets or sets the default Dalamud debug log level on startup.
- ///
- public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
-
- ///
- /// Gets or sets a value indicating whether to write to log files synchronously.
- ///
- public bool LogSynchronously { get; set; } = false;
-
- ///
- /// Gets or sets a value indicating whether or not the debug log should scroll automatically.
- ///
- public bool LogAutoScroll { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not the debug log should open at startup.
- ///
- public bool LogOpenAtStartup { get; set; }
-
- ///
- /// Gets or sets a value indicating whether or not the dev bar should open at startup.
- ///
- public bool DevBarOpenAtStartup { get; set; }
-
- ///
- /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
- ///
- public bool AssertsEnabledAtStartup { get; set; }
-
- ///
- /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
- ///
- public bool IsDocking { get; set; }
-
- ///
- /// Gets or sets a value indicating whether viewports should always be disabled.
- ///
- public bool IsDisableViewport { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
- ///
- public bool IsGamepadNavigationEnabled { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not focus management is enabled.
- ///
- public bool IsFocusManagementEnabled { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
- ///
- public bool IsAntiAntiDebugEnabled { get; set; } = false;
-
- ///
- /// Gets or sets a value indicating whether to resume game main thread after plugins load.
- ///
- public bool IsResumeGameAfterPluginLoad { get; set; } = false;
-
- ///
- /// Gets or sets the kind of beta to download when matches the server value.
- ///
- public string DalamudBetaKind { get; set; }
-
- ///
- /// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
- /// It is reset immediately when read.
- ///
- public bool PluginSafeMode { get; set; }
-
- ///
- /// Gets or sets a value indicating the wait time between plugin unload and plugin assembly unload.
- /// Uses default value that may change between versions if set to null.
- ///
- public int? PluginWaitBeforeFree { get; set; }
-
- ///
- /// Gets or sets a list of saved styles.
- ///
- [JsonProperty("SavedStyles")]
- public List? SavedStylesOld { get; set; }
-
- ///
- /// Gets or sets a list of saved styles.
- ///
- [JsonProperty("SavedStylesVersioned")]
- public List? SavedStyles { get; set; }
-
- ///
- /// Gets or sets the name of the currently chosen style.
- ///
- public string ChosenStyle { get; set; } = "Dalamud Standard";
-
- ///
- /// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled.
- ///
- public bool DisableRmtFiltering { get; set; }
-
- ///
- /// Gets or sets the order of DTR elements, by title.
- ///
- public List? DtrOrder { get; set; }
-
- ///
- /// Gets or sets the list of ignored DTR elements, by title.
- ///
- public List? DtrIgnore { get; set; }
-
- ///
- /// Gets or sets the spacing used for DTR entries.
- ///
- public int DtrSpacing { get; set; } = 10;
-
- ///
- /// Gets or sets a value indicating whether to swap the
- /// direction in which elements are drawn in the DTR.
- /// False indicates that elements will be drawn from the end of
- /// the left side of the Server Info bar, and continue leftwards.
- /// True indicates the opposite.
- ///
- public bool DtrSwapDirection { get; set; } = false;
-
- ///
- /// Gets or sets a value indicating whether the title screen menu is shown.
- ///
- public bool ShowTsm { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether or not market board data should be uploaded.
- ///
- public bool IsMbCollect { get; set; } = true;
-
- ///
- /// Gets the ISO 639-1 two-letter code for the language of the effective Dalamud display language.
- ///
- public string EffectiveLanguage
- {
- get
- {
- var languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
- try
- {
- if (string.IsNullOrEmpty(this.LanguageOverride))
- {
- var currentUiLang = CultureInfo.CurrentUICulture;
-
- if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x))
- return currentUiLang.TwoLetterISOLanguageName;
- else
- return languages[0];
- }
- else
- {
- return this.LanguageOverride;
- }
- }
- catch (Exception)
- {
- return languages[0];
- }
- }
- }
-
- ///
- /// Gets or sets a value indicating whether or not to show info on dev bar.
- ///
- public bool ShowDevBarInfo { get; set; } = true;
-
- ///
- /// Gets or sets the last-used contact details for the plugin feedback form.
- ///
- public string LastFeedbackContactDetails { get; set; } = string.Empty;
-
- ///
- /// Gets or sets a list of plugins that testing builds should be downloaded for.
- ///
- public List? PluginTestingOptIns { get; set; }
-
- ///
- /// Load a configuration from the provided path.
- ///
- /// The path to load the configuration file from.
- /// The deserialized configuration file.
- public static DalamudConfiguration Load(string path)
- {
- DalamudConfiguration deserialized = null;
+ var languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
try
{
- deserialized = JsonConvert.DeserializeObject(File.ReadAllText(path), SerializerSettings);
+ if (string.IsNullOrEmpty(this.LanguageOverride))
+ {
+ var currentUiLang = CultureInfo.CurrentUICulture;
+
+ if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x))
+ return currentUiLang.TwoLetterISOLanguageName;
+ else
+ return languages[0];
+ }
+ else
+ {
+ return this.LanguageOverride;
+ }
}
- catch (Exception ex)
+ catch (Exception)
{
- Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path);
+ return languages[0];
}
-
- deserialized ??= new DalamudConfiguration();
- deserialized.configPath = path;
-
- return deserialized;
- }
-
- ///
- /// Save the configuration at the path it was loaded from.
- ///
- public void Save()
- {
- File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
- this.DalamudConfigurationSaved?.Invoke(this);
}
}
+
+ ///
+ /// Gets or sets a value indicating whether or not to show info on dev bar.
+ ///
+ public bool ShowDevBarInfo { get; set; } = true;
+
+ ///
+ /// Gets or sets the last-used contact details for the plugin feedback form.
+ ///
+ public string LastFeedbackContactDetails { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets a list of plugins that testing builds should be downloaded for.
+ ///
+ public List? PluginTestingOptIns { get; set; }
+
+ ///
+ /// Load a configuration from the provided path.
+ ///
+ /// The path to load the configuration file from.
+ /// The deserialized configuration file.
+ public static DalamudConfiguration Load(string path)
+ {
+ DalamudConfiguration deserialized = null;
+ try
+ {
+ deserialized = JsonConvert.DeserializeObject(File.ReadAllText(path), SerializerSettings);
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path);
+ }
+
+ deserialized ??= new DalamudConfiguration();
+ deserialized.configPath = path;
+
+ return deserialized;
+ }
+
+ ///
+ /// Save the configuration at the path it was loaded from.
+ ///
+ public void Save()
+ {
+ File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
+ this.DalamudConfigurationSaved?.Invoke(this);
+ }
}
diff --git a/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs b/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs
index 995fb1a23..de083858d 100644
--- a/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs
+++ b/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs
@@ -1,24 +1,23 @@
-namespace Dalamud.Configuration
+namespace Dalamud.Configuration;
+
+///
+/// Additional locations to load dev plugins from.
+///
+internal sealed class DevPluginLocationSettings
{
///
- /// Additional locations to load dev plugins from.
+ /// Gets or sets the dev pluign path.
///
- internal sealed class DevPluginLocationSettings
- {
- ///
- /// Gets or sets the dev pluign path.
- ///
- public string Path { get; set; }
+ public string Path { get; set; }
- ///
- /// Gets or sets a value indicating whether the third party repo is enabled.
- ///
- public bool IsEnabled { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the third party repo is enabled.
+ ///
+ public bool IsEnabled { get; set; }
- ///
- /// Clone this object.
- ///
- /// A shallow copy of this object.
- public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
- }
+ ///
+ /// Clone this object.
+ ///
+ /// A shallow copy of this object.
+ public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
}
diff --git a/Dalamud/Configuration/Internal/DevPluginSettings.cs b/Dalamud/Configuration/Internal/DevPluginSettings.cs
index 17350cba0..939b03eca 100644
--- a/Dalamud/Configuration/Internal/DevPluginSettings.cs
+++ b/Dalamud/Configuration/Internal/DevPluginSettings.cs
@@ -1,18 +1,17 @@
-namespace Dalamud.Configuration.Internal
+namespace Dalamud.Configuration.Internal;
+
+///
+/// Settings for DevPlugins.
+///
+internal sealed class DevPluginSettings
{
///
- /// Settings for DevPlugins.
+ /// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
///
- internal sealed class DevPluginSettings
- {
- ///
- /// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
- ///
- public bool StartOnBoot { get; set; } = true;
+ public bool StartOnBoot { get; set; } = true;
- ///
- /// Gets or sets a value indicating whether this plugin should automatically reload on file change.
- ///
- public bool AutomaticReloading { get; set; } = false;
- }
+ ///
+ /// Gets or sets a value indicating whether this plugin should automatically reload on file change.
+ ///
+ public bool AutomaticReloading { get; set; } = false;
}
diff --git a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs
index 99a4c6709..a251da763 100644
--- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs
+++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs
@@ -1,38 +1,37 @@
using System;
-namespace Dalamud.Configuration.Internal
+namespace Dalamud.Configuration.Internal;
+
+///
+/// Environmental configuration settings.
+///
+internal class EnvironmentConfiguration
{
///
- /// Environmental configuration settings.
+ /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
///
- internal class EnvironmentConfiguration
- {
- ///
- /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
- ///
- public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
+ public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
- ///
- /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
- ///
- public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
+ ///
+ /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
+ ///
+ public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
- ///
- /// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
- ///
- public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
+ ///
+ /// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
+ ///
+ public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
- ///
- /// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
- ///
- public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
+ ///
+ /// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
+ ///
+ public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
- ///
- /// Gets a value indicating whether or not Dalamud context menus should be disabled.
- ///
- public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU");
+ ///
+ /// Gets a value indicating whether or not Dalamud context menus should be disabled.
+ ///
+ public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU");
- private static bool GetEnvironmentVariable(string name)
- => bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
- }
+ private static bool GetEnvironmentVariable(string name)
+ => bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
}
diff --git a/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs b/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs
index cafb96a47..070fda408 100644
--- a/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs
+++ b/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs
@@ -1,29 +1,28 @@
-namespace Dalamud.Configuration
+namespace Dalamud.Configuration;
+
+///
+/// Third party repository for dalamud plugins.
+///
+internal sealed class ThirdPartyRepoSettings
{
///
- /// Third party repository for dalamud plugins.
+ /// Gets or sets the third party repo url.
///
- internal sealed class ThirdPartyRepoSettings
- {
- ///
- /// Gets or sets the third party repo url.
- ///
- public string Url { get; set; }
+ public string Url { get; set; }
- ///
- /// Gets or sets a value indicating whether the third party repo is enabled.
- ///
- public bool IsEnabled { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the third party repo is enabled.
+ ///
+ public bool IsEnabled { get; set; }
- ///
- /// Gets or sets a short name for the repo url.
- ///
- public string Name { get; set; }
+ ///
+ /// Gets or sets a short name for the repo url.
+ ///
+ public string Name { get; set; }
- ///
- /// Clone this object.
- ///
- /// A shallow copy of this object.
- public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
- }
+ ///
+ /// Clone this object.
+ ///
+ /// A shallow copy of this object.
+ public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
}
diff --git a/Dalamud/Configuration/PluginConfigurations.cs b/Dalamud/Configuration/PluginConfigurations.cs
index 48522ea56..b917a9e79 100644
--- a/Dalamud/Configuration/PluginConfigurations.cs
+++ b/Dalamud/Configuration/PluginConfigurations.cs
@@ -2,149 +2,148 @@ using System.IO;
using Newtonsoft.Json;
-namespace Dalamud.Configuration
+namespace Dalamud.Configuration;
+
+///
+/// Configuration to store settings for a dalamud plugin.
+///
+public sealed class PluginConfigurations
{
+ private readonly DirectoryInfo configDirectory;
+
///
- /// Configuration to store settings for a dalamud plugin.
+ /// Initializes a new instance of the class.
///
- public sealed class PluginConfigurations
+ /// Directory for storage of plugin configuration files.
+ public PluginConfigurations(string storageFolder)
{
- private readonly DirectoryInfo configDirectory;
+ this.configDirectory = new DirectoryInfo(storageFolder);
+ this.configDirectory.Create();
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// Directory for storage of plugin configuration files.
- public PluginConfigurations(string storageFolder)
+ ///
+ /// Save/Load plugin configuration.
+ /// NOTE: Save/Load are still using Type information for now,
+ /// despite LoadForType superseding Load and not requiring or using it.
+ /// It might be worth removing the Type info from Save, to strip it from all future saved configs,
+ /// and then Load() can probably be removed entirely.
+ ///
+ /// Plugin configuration.
+ /// Plugin name.
+ public void Save(IPluginConfiguration config, string pluginName)
+ {
+ File.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config));
+ }
+
+ ///
+ /// Load plugin configuration.
+ ///
+ /// Plugin name.
+ /// Plugin configuration.
+ public IPluginConfiguration? Load(string pluginName)
+ {
+ var path = this.GetConfigFile(pluginName);
+
+ if (!path.Exists)
+ return null;
+
+ return DeserializeConfig(File.ReadAllText(path.FullName));
+ }
+
+ ///
+ /// Delete the configuration file and folder for the specified plugin.
+ /// This will throw an if the plugin did not correctly close its handles.
+ ///
+ /// The name of the plugin.
+ public void Delete(string pluginName)
+ {
+ var directory = this.GetDirectoryPath(pluginName);
+ if (directory.Exists)
+ directory.Delete(true);
+
+ var file = this.GetConfigFile(pluginName);
+ if (file.Exists)
+ file.Delete();
+ }
+
+ ///
+ /// Get plugin directory.
+ ///
+ /// Plugin name.
+ /// Plugin directory path.
+ public string GetDirectory(string pluginName)
+ {
+ try
{
- this.configDirectory = new DirectoryInfo(storageFolder);
- this.configDirectory.Create();
- }
-
- ///
- /// Save/Load plugin configuration.
- /// NOTE: Save/Load are still using Type information for now,
- /// despite LoadForType superseding Load and not requiring or using it.
- /// It might be worth removing the Type info from Save, to strip it from all future saved configs,
- /// and then Load() can probably be removed entirely.
- ///
- /// Plugin configuration.
- /// Plugin name.
- public void Save(IPluginConfiguration config, string pluginName)
- {
- File.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config));
- }
-
- ///
- /// Load plugin configuration.
- ///
- /// Plugin name.
- /// Plugin configuration.
- public IPluginConfiguration? Load(string pluginName)
- {
- var path = this.GetConfigFile(pluginName);
-
+ var path = this.GetDirectoryPath(pluginName);
if (!path.Exists)
- return null;
-
- return DeserializeConfig(File.ReadAllText(path.FullName));
- }
-
- ///
- /// Delete the configuration file and folder for the specified plugin.
- /// This will throw an if the plugin did not correctly close its handles.
- ///
- /// The name of the plugin.
- public void Delete(string pluginName)
- {
- var directory = this.GetDirectoryPath(pluginName);
- if (directory.Exists)
- directory.Delete(true);
-
- var file = this.GetConfigFile(pluginName);
- if (file.Exists)
- file.Delete();
- }
-
- ///
- /// Get plugin directory.
- ///
- /// Plugin name.
- /// Plugin directory path.
- public string GetDirectory(string pluginName)
- {
- try
{
- var path = this.GetDirectoryPath(pluginName);
- if (!path.Exists)
- {
- path.Create();
- }
-
- return path.FullName;
- }
- catch
- {
- return string.Empty;
+ path.Create();
}
+
+ return path.FullName;
}
-
- ///
- /// Load Plugin configuration. Parameterized deserialization.
- /// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig().
- /// Eventually there may be an additional pluginInterface method that can call this directly
- /// without reflection - for now this is in support of the existing plugin api.
- ///
- /// Plugin Name.
- /// Configuration Type.
- /// Plugin Configuration.
- public T LoadForType(string pluginName) where T : IPluginConfiguration
+ catch
{
- var path = this.GetConfigFile(pluginName);
-
- return !path.Exists ? default : JsonConvert.DeserializeObject(File.ReadAllText(path.FullName));
-
- // intentionally no type handling - it will break when updating a plugin at runtime
- // and turns out to be unnecessary when we fully qualify the object type
+ return string.Empty;
}
+ }
- ///
- /// Get FileInfo to plugin config file.
- ///
- /// InternalName of the plugin.
- /// FileInfo of the config file.
- public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
+ ///
+ /// Load Plugin configuration. Parameterized deserialization.
+ /// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig().
+ /// Eventually there may be an additional pluginInterface method that can call this directly
+ /// without reflection - for now this is in support of the existing plugin api.
+ ///
+ /// Plugin Name.
+ /// Configuration Type.
+ /// Plugin Configuration.
+ public T LoadForType(string pluginName) where T : IPluginConfiguration
+ {
+ var path = this.GetConfigFile(pluginName);
- ///
- /// Serializes a plugin configuration object.
- ///
- /// The configuration object.
- /// A string representing the serialized configuration object.
- internal static string SerializeConfig(object? config)
+ return !path.Exists ? default : JsonConvert.DeserializeObject(File.ReadAllText(path.FullName));
+
+ // intentionally no type handling - it will break when updating a plugin at runtime
+ // and turns out to be unnecessary when we fully qualify the object type
+ }
+
+ ///
+ /// Get FileInfo to plugin config file.
+ ///
+ /// InternalName of the plugin.
+ /// FileInfo of the config file.
+ public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
+
+ ///
+ /// Serializes a plugin configuration object.
+ ///
+ /// The configuration object.
+ /// A string representing the serialized configuration object.
+ internal static string SerializeConfig(object? config)
+ {
+ return JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
{
- return JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
+ TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
+ TypeNameHandling = TypeNameHandling.Objects,
+ });
+ }
+
+ ///
+ /// Deserializes a plugin configuration from a string.
+ ///
+ /// The serialized configuration.
+ /// The configuration object, or null.
+ internal static IPluginConfiguration? DeserializeConfig(string data)
+ {
+ return JsonConvert.DeserializeObject(
+ data,
+ new JsonSerializerSettings
{
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
TypeNameHandling = TypeNameHandling.Objects,
});
- }
-
- ///
- /// Deserializes a plugin configuration from a string.
- ///
- /// The serialized configuration.
- /// The configuration object, or null.
- internal static IPluginConfiguration? DeserializeConfig(string data)
- {
- return JsonConvert.DeserializeObject(
- data,
- new JsonSerializerSettings
- {
- TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
- TypeNameHandling = TypeNameHandling.Objects,
- });
- }
-
- private DirectoryInfo GetDirectoryPath(string pluginName) => new(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 d78c36692..37375d87b 100644
--- a/Dalamud/Dalamud.cs
+++ b/Dalamud/Dalamud.cs
@@ -19,131 +19,130 @@ using Serilog;
[assembly: InternalsVisibleTo("Dalamud.Test")]
[assembly: InternalsVisibleTo("Dalamud.DevHelpers")]
-namespace Dalamud
+namespace Dalamud;
+
+///
+/// The main Dalamud class containing all subsystems.
+///
+internal sealed class Dalamud : IServiceType
{
+ #region Internals
+
+ private readonly ManualResetEvent unloadSignal;
+
+ #endregion
+
///
- /// The main Dalamud class containing all subsystems.
+ /// Initializes a new instance of the class.
///
- internal sealed class Dalamud : IServiceType
+ /// DalamudStartInfo instance.
+ /// The Dalamud configuration.
+ /// Event used to signal the main thread to continue.
+ public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
{
- #region Internals
+ this.unloadSignal = new ManualResetEvent(false);
+ this.unloadSignal.Reset();
- private readonly ManualResetEvent unloadSignal;
+ ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
- #endregion
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// DalamudStartInfo instance.
- /// The Dalamud configuration.
- /// Event used to signal the main thread to continue.
- public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
+ if (!configuration.IsResumeGameAfterPluginLoad)
{
- this.unloadSignal = new ManualResetEvent(false);
- this.unloadSignal.Reset();
-
- ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
-
- if (!configuration.IsResumeGameAfterPluginLoad)
+ NativeFunctions.SetEvent(mainThreadContinueEvent);
+ try
+ {
+ _ = ServiceManager.InitializeEarlyLoadableServices();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Service initialization failure");
+ }
+ }
+ else
+ {
+ Task.Run(async () =>
{
- NativeFunctions.SetEvent(mainThreadContinueEvent);
try
{
- _ = ServiceManager.InitializeEarlyLoadableServices();
+ var tasks = new[]
+ {
+ ServiceManager.InitializeEarlyLoadableServices(),
+ ServiceManager.BlockingResolved,
+ };
+
+ await Task.WhenAny(tasks);
+ var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray();
+ if (faultedTasks.Any())
+ throw new AggregateException(faultedTasks);
+
+ NativeFunctions.SetEvent(mainThreadContinueEvent);
+
+ await Task.WhenAll(tasks);
}
catch (Exception e)
{
Log.Error(e, "Service initialization failure");
}
- }
- else
- {
- Task.Run(async () =>
+ finally
{
- try
- {
- var tasks = new[]
- {
- ServiceManager.InitializeEarlyLoadableServices(),
- ServiceManager.BlockingResolved,
- };
-
- await Task.WhenAny(tasks);
- var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray();
- if (faultedTasks.Any())
- throw new AggregateException(faultedTasks);
-
- NativeFunctions.SetEvent(mainThreadContinueEvent);
-
- await Task.WhenAll(tasks);
- }
- catch (Exception e)
- {
- Log.Error(e, "Service initialization failure");
- }
- finally
- {
- NativeFunctions.SetEvent(mainThreadContinueEvent);
- }
- });
- }
- }
-
- ///
- /// Gets location of stored assets.
- ///
- internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory!);
-
- ///
- /// Queue an unload of Dalamud when it gets the chance.
- ///
- public void Unload()
- {
- Log.Information("Trigger unload");
- this.unloadSignal.Set();
- }
-
- ///
- /// Wait for an unload request to start.
- ///
- public void WaitForUnload()
- {
- this.unloadSignal.WaitOne();
- }
-
- ///
- /// Dispose subsystems related to plugin handling.
- ///
- public void DisposePlugins()
- {
- // this must be done before unloading interface manager, in order to do rebuild
- // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
- // will not receive any windows messages
- Service.GetNullable()?.Dispose();
-
- // this must be done before unloading plugins, or it can cause a race condition
- // due to rendering happening on another thread, where a plugin might receive
- // a render call after it has been disposed, which can crash if it attempts to
- // use any resources that it freed in its own Dispose method
- Service.GetNullable()?.Dispose();
-
- Service.GetNullable()?.Dispose();
-
- Service.GetNullable()?.Dispose();
- }
-
- ///
- /// Replace the built-in exception handler with a debug one.
- ///
- internal void ReplaceExceptionHandler()
- {
- var releaseSig = "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 = Service.Get().ScanText(releaseSig);
- Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
-
- var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
- Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
+ NativeFunctions.SetEvent(mainThreadContinueEvent);
+ }
+ });
}
}
+
+ ///
+ /// Gets location of stored assets.
+ ///
+ internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory!);
+
+ ///
+ /// Queue an unload of Dalamud when it gets the chance.
+ ///
+ public void Unload()
+ {
+ Log.Information("Trigger unload");
+ this.unloadSignal.Set();
+ }
+
+ ///
+ /// Wait for an unload request to start.
+ ///
+ public void WaitForUnload()
+ {
+ this.unloadSignal.WaitOne();
+ }
+
+ ///
+ /// Dispose subsystems related to plugin handling.
+ ///
+ public void DisposePlugins()
+ {
+ // this must be done before unloading interface manager, in order to do rebuild
+ // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
+ // will not receive any windows messages
+ Service.GetNullable()?.Dispose();
+
+ // this must be done before unloading plugins, or it can cause a race condition
+ // due to rendering happening on another thread, where a plugin might receive
+ // a render call after it has been disposed, which can crash if it attempts to
+ // use any resources that it freed in its own Dispose method
+ Service.GetNullable()?.Dispose();
+
+ Service.GetNullable()?.Dispose();
+
+ Service.GetNullable()?.Dispose();
+ }
+
+ ///
+ /// Replace the built-in exception handler with a debug one.
+ ///
+ internal void ReplaceExceptionHandler()
+ {
+ var releaseSig = "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 = Service.Get().ScanText(releaseSig);
+ Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
+
+ var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
+ Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
+ }
}
diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs
index 434624b3b..0010741bf 100644
--- a/Dalamud/DalamudStartInfo.cs
+++ b/Dalamud/DalamudStartInfo.cs
@@ -4,167 +4,166 @@ using System.Collections.Generic;
using Dalamud.Game;
using Newtonsoft.Json;
-namespace Dalamud
+namespace Dalamud;
+
+///
+/// Struct containing information needed to initialize Dalamud.
+///
+[Serializable]
+public record DalamudStartInfo : IServiceType
{
///
- /// Struct containing information needed to initialize Dalamud.
+ /// Initializes a new instance of the class.
///
- [Serializable]
- public record DalamudStartInfo : IServiceType
+ public DalamudStartInfo()
{
- ///
- /// Initializes a new instance of the class.
- ///
- public DalamudStartInfo()
- {
- // ignored
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Object to copy values from.
- public DalamudStartInfo(DalamudStartInfo other)
- {
- this.WorkingDirectory = other.WorkingDirectory;
- this.ConfigurationPath = other.ConfigurationPath;
- this.PluginDirectory = other.PluginDirectory;
- this.DefaultPluginDirectory = other.DefaultPluginDirectory;
- this.AssetDirectory = other.AssetDirectory;
- this.Language = other.Language;
- this.GameVersion = other.GameVersion;
- this.DelayInitializeMs = other.DelayInitializeMs;
- this.TroubleshootingPackData = other.TroubleshootingPackData;
- this.NoLoadPlugins = other.NoLoadPlugins;
- this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins;
- this.BootLogPath = other.BootLogPath;
- this.BootShowConsole = other.BootShowConsole;
- this.BootDisableFallbackConsole = other.BootDisableFallbackConsole;
- this.BootWaitMessageBox = other.BootWaitMessageBox;
- this.BootWaitDebugger = other.BootWaitDebugger;
- this.BootVehEnabled = other.BootVehEnabled;
- this.BootVehFull = other.BootVehFull;
- this.BootEnableEtw = other.BootEnableEtw;
- this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
- this.BootEnabledGameFixes = other.BootEnabledGameFixes;
- this.BootUnhookDlls = other.BootUnhookDlls;
- this.CrashHandlerShow = other.CrashHandlerShow;
- }
-
- ///
- /// Gets or sets the working directory of the XIVLauncher installations.
- ///
- public string? WorkingDirectory { get; set; }
-
- ///
- /// Gets or sets the path to the configuration file.
- ///
- public string? ConfigurationPath { get; set; }
-
- ///
- /// Gets or sets the path to the directory for installed plugins.
- ///
- public string? PluginDirectory { get; set; }
-
- ///
- /// Gets or sets the path to the directory for developer plugins.
- ///
- public string? DefaultPluginDirectory { get; set; }
-
- ///
- /// Gets or sets the path to core Dalamud assets.
- ///
- public string? AssetDirectory { get; set; }
-
- ///
- /// Gets or sets the language of the game client.
- ///
- public ClientLanguage Language { get; set; } = ClientLanguage.English;
-
- ///
- /// Gets or sets the current game version code.
- ///
- [JsonConverter(typeof(GameVersionConverter))]
- public GameVersion? GameVersion { get; set; }
-
- ///
- /// Gets or sets troubleshooting information to attach when generating a tspack file.
- ///
- public string TroubleshootingPackData { get; set; }
-
- ///
- /// Gets or sets a value that specifies how much to wait before a new Dalamud session.
- ///
- public int DelayInitializeMs { get; set; }
-
- ///
- /// Gets or sets a value indicating whether no plugins should be loaded.
- ///
- public bool NoLoadPlugins { get; set; }
-
- ///
- /// Gets or sets a value indicating whether no third-party plugins should be loaded.
- ///
- public bool NoLoadThirdPartyPlugins { get; set; }
-
- ///
- /// Gets or sets the path the boot log file is supposed to be written to.
- ///
- public string? BootLogPath { get; set; }
-
- ///
- /// Gets or sets a value indicating whether a Boot console should be shown.
- ///
- public bool BootShowConsole { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the fallback console should be shown, if needed.
- ///
- public bool BootDisableFallbackConsole { get; set; }
-
- ///
- /// Gets or sets a flag indicating where Dalamud should wait with a message box.
- ///
- public int BootWaitMessageBox { get; set; }
-
- ///
- /// Gets or sets a value indicating whether Dalamud should wait for a debugger to be attached before initializing.
- ///
- public bool BootWaitDebugger { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the VEH should be enabled.
- ///
- public bool BootVehEnabled { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the VEH should be doing full crash dumps.
- ///
- public bool BootVehFull { get; set; }
-
- ///
- /// Gets or sets a value indicating whether or not ETW should be enabled.
- ///
- public bool BootEnableEtw { get; set; }
-
- ///
- /// Gets or sets a value choosing the OpenProcess hookmode.
- ///
- public int BootDotnetOpenProcessHookMode { get; set; }
-
- ///
- /// Gets or sets a list of enabled game fixes.
- ///
- public List? BootEnabledGameFixes { get; set; }
-
- ///
- /// Gets or sets a list of DLLs that should be unhooked.
- ///
- public List? BootUnhookDlls { get; set; }
-
- ///
- /// Gets or sets a value indicating whether to show crash handler console window.
- ///
- public bool CrashHandlerShow { get; set; }
+ // ignored
}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Object to copy values from.
+ public DalamudStartInfo(DalamudStartInfo other)
+ {
+ this.WorkingDirectory = other.WorkingDirectory;
+ this.ConfigurationPath = other.ConfigurationPath;
+ this.PluginDirectory = other.PluginDirectory;
+ this.DefaultPluginDirectory = other.DefaultPluginDirectory;
+ this.AssetDirectory = other.AssetDirectory;
+ this.Language = other.Language;
+ this.GameVersion = other.GameVersion;
+ this.DelayInitializeMs = other.DelayInitializeMs;
+ this.TroubleshootingPackData = other.TroubleshootingPackData;
+ this.NoLoadPlugins = other.NoLoadPlugins;
+ this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins;
+ this.BootLogPath = other.BootLogPath;
+ this.BootShowConsole = other.BootShowConsole;
+ this.BootDisableFallbackConsole = other.BootDisableFallbackConsole;
+ this.BootWaitMessageBox = other.BootWaitMessageBox;
+ this.BootWaitDebugger = other.BootWaitDebugger;
+ this.BootVehEnabled = other.BootVehEnabled;
+ this.BootVehFull = other.BootVehFull;
+ this.BootEnableEtw = other.BootEnableEtw;
+ this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
+ this.BootEnabledGameFixes = other.BootEnabledGameFixes;
+ this.BootUnhookDlls = other.BootUnhookDlls;
+ this.CrashHandlerShow = other.CrashHandlerShow;
+ }
+
+ ///
+ /// Gets or sets the working directory of the XIVLauncher installations.
+ ///
+ public string? WorkingDirectory { get; set; }
+
+ ///
+ /// Gets or sets the path to the configuration file.
+ ///
+ public string? ConfigurationPath { get; set; }
+
+ ///
+ /// Gets or sets the path to the directory for installed plugins.
+ ///
+ public string? PluginDirectory { get; set; }
+
+ ///
+ /// Gets or sets the path to the directory for developer plugins.
+ ///
+ public string? DefaultPluginDirectory { get; set; }
+
+ ///
+ /// Gets or sets the path to core Dalamud assets.
+ ///
+ public string? AssetDirectory { get; set; }
+
+ ///
+ /// Gets or sets the language of the game client.
+ ///
+ public ClientLanguage Language { get; set; } = ClientLanguage.English;
+
+ ///
+ /// Gets or sets the current game version code.
+ ///
+ [JsonConverter(typeof(GameVersionConverter))]
+ public GameVersion? GameVersion { get; set; }
+
+ ///
+ /// Gets or sets troubleshooting information to attach when generating a tspack file.
+ ///
+ public string TroubleshootingPackData { get; set; }
+
+ ///
+ /// Gets or sets a value that specifies how much to wait before a new Dalamud session.
+ ///
+ public int DelayInitializeMs { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether no plugins should be loaded.
+ ///
+ public bool NoLoadPlugins { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether no third-party plugins should be loaded.
+ ///
+ public bool NoLoadThirdPartyPlugins { get; set; }
+
+ ///
+ /// Gets or sets the path the boot log file is supposed to be written to.
+ ///
+ public string? BootLogPath { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a Boot console should be shown.
+ ///
+ public bool BootShowConsole { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the fallback console should be shown, if needed.
+ ///
+ public bool BootDisableFallbackConsole { get; set; }
+
+ ///
+ /// Gets or sets a flag indicating where Dalamud should wait with a message box.
+ ///
+ public int BootWaitMessageBox { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether Dalamud should wait for a debugger to be attached before initializing.
+ ///
+ public bool BootWaitDebugger { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the VEH should be enabled.
+ ///
+ public bool BootVehEnabled { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the VEH should be doing full crash dumps.
+ ///
+ public bool BootVehFull { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether or not ETW should be enabled.
+ ///
+ public bool BootEnableEtw { get; set; }
+
+ ///
+ /// Gets or sets a value choosing the OpenProcess hookmode.
+ ///
+ public int BootDotnetOpenProcessHookMode { get; set; }
+
+ ///
+ /// Gets or sets a list of enabled game fixes.
+ ///
+ public List? BootEnabledGameFixes { get; set; }
+
+ ///
+ /// Gets or sets a list of DLLs that should be unhooked.
+ ///
+ public List? BootUnhookDlls { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to show crash handler console window.
+ ///
+ public bool CrashHandlerShow { get; set; }
}
diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs
index 48cd0d325..8cdb58dcd 100644
--- a/Dalamud/Data/DataManager.cs
+++ b/Dalamud/Data/DataManager.cs
@@ -19,331 +19,330 @@ using Lumina.Excel;
using Newtonsoft.Json;
using Serilog;
-namespace Dalamud.Data
+namespace Dalamud.Data;
+
+///
+/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public sealed class DataManager : IDisposable, IServiceType
{
- ///
- /// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public sealed class DataManager : IDisposable, IServiceType
+ private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
+
+ private readonly Thread luminaResourceThread;
+ private readonly CancellationTokenSource luminaCancellationTokenSource;
+
+ [ServiceManager.ServiceConstructor]
+ private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud)
{
- private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
+ this.Language = dalamudStartInfo.Language;
- private readonly Thread luminaResourceThread;
- private readonly CancellationTokenSource luminaCancellationTokenSource;
+ // Set up default values so plugins do not null-reference when data is being loaded.
+ this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary());
- [ServiceManager.ServiceConstructor]
- private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud)
+ var baseDir = dalamud.AssetDirectory.FullName;
+ try
{
- this.Language = dalamudStartInfo.Language;
+ Log.Verbose("Starting data load...");
- // Set up default values so plugins do not null-reference when data is being loaded.
- this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary());
+ var zoneOpCodeDict = JsonConvert.DeserializeObject>(
+ File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")))!;
+ this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict);
- var baseDir = dalamud.AssetDirectory.FullName;
- try
+ 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);
+
+ using (Timings.Start("Lumina Init"))
{
- 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);
-
- using (Timings.Start("Lumina Init"))
+ var luminaOptions = new LuminaOptions
{
- var luminaOptions = new LuminaOptions
- {
- LoadMultithreaded = true,
- CacheFileResources = true,
+ LoadMultithreaded = true,
+ CacheFileResources = true,
#if DEBUG
PanicOnSheetChecksumMismatch = true,
#else
- PanicOnSheetChecksumMismatch = false,
+ PanicOnSheetChecksumMismatch = false,
#endif
- DefaultExcelLanguage = this.Language.ToLumina(),
- };
+ DefaultExcelLanguage = this.Language.ToLumina(),
+ };
- var processModule = Process.GetCurrentProcess().MainModule;
- if (processModule != null)
+ var processModule = Process.GetCurrentProcess().MainModule;
+ if (processModule != null)
+ {
+ this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName)!, "sqpack"), luminaOptions);
+ }
+ else
+ {
+ throw new Exception("Could not main module.");
+ }
+
+ Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
+ }
+
+ this.IsDataReady = true;
+
+ this.luminaCancellationTokenSource = new();
+
+ var luminaCancellationToken = this.luminaCancellationTokenSource.Token;
+ this.luminaResourceThread = new(() =>
+ {
+ while (!luminaCancellationToken.IsCancellationRequested)
+ {
+ if (this.GameData.FileHandleManager.HasPendingFileLoads)
{
- this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName)!, "sqpack"), luminaOptions);
+ this.GameData.ProcessFileHandleQueue();
}
else
{
- throw new Exception("Could not main module.");
+ Thread.Sleep(5);
}
-
- Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
}
-
- this.IsDataReady = true;
-
- this.luminaCancellationTokenSource = new();
-
- var luminaCancellationToken = this.luminaCancellationTokenSource.Token;
- this.luminaResourceThread = new(() =>
- {
- while (!luminaCancellationToken.IsCancellationRequested)
- {
- if (this.GameData.FileHandleManager.HasPendingFileLoads)
- {
- this.GameData.ProcessFileHandleQueue();
- }
- else
- {
- Thread.Sleep(5);
- }
- }
- });
- this.luminaResourceThread.Start();
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Could not download data.");
- }
+ });
+ this.luminaResourceThread.Start();
}
-
- ///
- /// Gets the current game client language.
- ///
- public ClientLanguage Language { get; private set; }
-
- ///
- /// Gets the OpCodes sent by the server to the client.
- ///
- public ReadOnlyDictionary ServerOpCodes { get; private set; }
-
- ///
- /// Gets the OpCodes sent by the client to the server.
- ///
- [UsedImplicitly]
- public ReadOnlyDictionary ClientOpCodes { get; private set; }
-
- ///
- /// Gets a object which gives access to any excel/game data.
- ///
- public GameData GameData { get; private set; }
-
- ///
- /// Gets an object which gives access to any of the game's sheet data.
- ///
- public ExcelModule Excel => this.GameData.Excel;
-
- ///
- /// Gets a value indicating whether Game Data is ready to be read.
- ///
- public bool IsDataReady { get; private set; }
-
- #region Lumina Wrappers
-
- ///
- /// Get an with the given Excel sheet row type.
- ///
- /// The excel sheet type to get.
- /// The , giving access to game rows.
- public ExcelSheet? GetExcelSheet() where T : ExcelRow
+ catch (Exception ex)
{
- return this.Excel.GetSheet();
- }
-
- ///
- /// Get an with the given Excel sheet row type with a specified language.
- ///
- /// Language of the sheet to get.
- /// The excel sheet type to get.
- /// The , giving access to game rows.
- public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow
- {
- return this.Excel.GetSheet(language.ToLumina());
- }
-
- ///
- /// Get a with the given path.
- ///
- /// The path inside of the game files.
- /// The of the file.
- public FileResource? GetFile(string path)
- {
- return this.GetFile(path);
- }
-
- ///
- /// Get a with the given path, of the given type.
- ///
- /// The type of resource.
- /// The path inside of the game files.
- /// The of the file.
- public T? GetFile(string path) where T : FileResource
- {
- 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;
- }
-
- ///
- /// Check if the file with the given path exists within the game's index files.
- ///
- /// The path inside of the game files.
- /// True if the file exists.
- public bool FileExists(string path)
- {
- return this.GameData.FileExists(path);
- }
-
- ///
- /// Get a containing the icon with the given ID.
- ///
- /// The icon ID.
- /// The containing the icon.
- public TexFile? GetIcon(uint iconId)
- {
- return this.GetIcon(this.Language, iconId);
- }
-
- ///
- /// Get a containing the icon with the given ID, of the given quality.
- ///
- /// A value indicating whether the icon should be HQ.
- /// The icon ID.
- /// The containing the icon.
- public TexFile? GetIcon(bool isHq, uint iconId)
- {
- var type = isHq ? "hq/" : string.Empty;
- return this.GetIcon(type, iconId);
- }
-
- ///
- /// Get a containing the icon with the given ID, of the given language.
- ///
- /// The requested language.
- /// The icon ID.
- /// The containing the icon.
- public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
- {
- var type = iconLanguage switch
- {
- ClientLanguage.Japanese => "ja/",
- ClientLanguage.English => "en/",
- ClientLanguage.German => "de/",
- ClientLanguage.French => "fr/",
- _ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
- };
-
- return this.GetIcon(type, iconId);
- }
-
- ///
- /// Get a containing the icon with the given ID, of the given type.
- ///
- /// 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 TexFile? GetIcon(string? type, uint iconId)
- {
- type ??= string.Empty;
- if (type.Length > 0 && !type.EndsWith("/"))
- type += "/";
-
- var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId);
- var file = this.GetFile(filePath);
-
- if (type == string.Empty || file != default)
- return file;
-
- // Couldn't get specific type, try for generic version.
- filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
- file = this.GetFile(filePath);
- return file;
- }
-
- ///
- /// Get a containing the HQ icon with the given ID.
- ///
- /// The icon ID.
- /// The containing the icon.
- public TexFile? GetHqIcon(uint iconId)
- => this.GetIcon(true, iconId);
-
- ///
- /// Get the passed as a drawable ImGui TextureWrap.
- ///
- /// The Lumina .
- /// A that can be used to draw the texture.
- public TextureWrap? GetImGuiTexture(TexFile? tex)
- {
- return tex == null ? null : Service.Get().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));
-
- ///
- /// Get a containing the icon with the given ID.
- ///
- /// The icon ID.
- /// The containing the icon.
- public TextureWrap? GetImGuiTextureIcon(uint iconId)
- => this.GetImGuiTexture(this.GetIcon(iconId));
-
- ///
- /// Get a containing the icon with the given ID, of the given quality.
- ///
- /// A value indicating whether the icon should be HQ.
- /// The icon ID.
- /// The containing the icon.
- public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
- => this.GetImGuiTexture(this.GetIcon(isHq, iconId));
-
- ///
- /// Get a containing the icon with the given ID, of the given language.
- ///
- /// The requested language.
- /// The icon ID.
- /// The containing the icon.
- public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
- => this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
-
- ///
- /// Get a containing the icon with the given ID, of the given type.
- ///
- /// 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, uint iconId)
- => this.GetImGuiTexture(this.GetIcon(type, iconId));
-
- ///
- /// Get a containing the HQ icon with the given ID.
- ///
- /// The icon ID.
- /// The containing the icon.
- public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
- => this.GetImGuiTexture(this.GetHqIcon(iconId));
-
- #endregion
-
- ///
- /// Dispose this DataManager.
- ///
- void IDisposable.Dispose()
- {
- this.luminaCancellationTokenSource.Cancel();
+ Log.Error(ex, "Could not download data.");
}
}
+
+ ///
+ /// Gets the current game client language.
+ ///
+ public ClientLanguage Language { get; private set; }
+
+ ///
+ /// Gets the OpCodes sent by the server to the client.
+ ///
+ public ReadOnlyDictionary ServerOpCodes { get; private set; }
+
+ ///
+ /// Gets the OpCodes sent by the client to the server.
+ ///
+ [UsedImplicitly]
+ public ReadOnlyDictionary ClientOpCodes { get; private set; }
+
+ ///
+ /// Gets a object which gives access to any excel/game data.
+ ///
+ public GameData GameData { get; private set; }
+
+ ///
+ /// Gets an object which gives access to any of the game's sheet data.
+ ///
+ public ExcelModule Excel => this.GameData.Excel;
+
+ ///
+ /// Gets a value indicating whether Game Data is ready to be read.
+ ///
+ public bool IsDataReady { get; private set; }
+
+ #region Lumina Wrappers
+
+ ///
+ /// Get an with the given Excel sheet row type.
+ ///
+ /// The excel sheet type to get.
+ /// The , giving access to game rows.
+ public ExcelSheet? GetExcelSheet() where T : ExcelRow
+ {
+ return this.Excel.GetSheet();
+ }
+
+ ///
+ /// Get an with the given Excel sheet row type with a specified language.
+ ///
+ /// Language of the sheet to get.
+ /// The excel sheet type to get.
+ /// The , giving access to game rows.
+ public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow
+ {
+ return this.Excel.GetSheet(language.ToLumina());
+ }
+
+ ///
+ /// Get a with the given path.
+ ///
+ /// The path inside of the game files.
+ /// The of the file.
+ public FileResource? GetFile(string path)
+ {
+ return this.GetFile(path);
+ }
+
+ ///
+ /// Get a with the given path, of the given type.
+ ///
+ /// The type of resource.
+ /// The path inside of the game files.
+ /// The of the file.
+ public T? GetFile(string path) where T : FileResource
+ {
+ 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;
+ }
+
+ ///
+ /// Check if the file with the given path exists within the game's index files.
+ ///
+ /// The path inside of the game files.
+ /// True if the file exists.
+ public bool FileExists(string path)
+ {
+ return this.GameData.FileExists(path);
+ }
+
+ ///
+ /// Get a containing the icon with the given ID.
+ ///
+ /// The icon ID.
+ /// The containing the icon.
+ public TexFile? GetIcon(uint iconId)
+ {
+ return this.GetIcon(this.Language, iconId);
+ }
+
+ ///
+ /// Get a containing the icon with the given ID, of the given quality.
+ ///
+ /// A value indicating whether the icon should be HQ.
+ /// The icon ID.
+ /// The containing the icon.
+ public TexFile? GetIcon(bool isHq, uint iconId)
+ {
+ var type = isHq ? "hq/" : string.Empty;
+ return this.GetIcon(type, iconId);
+ }
+
+ ///
+ /// Get a containing the icon with the given ID, of the given language.
+ ///
+ /// The requested language.
+ /// The icon ID.
+ /// The containing the icon.
+ public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
+ {
+ var type = iconLanguage switch
+ {
+ ClientLanguage.Japanese => "ja/",
+ ClientLanguage.English => "en/",
+ ClientLanguage.German => "de/",
+ ClientLanguage.French => "fr/",
+ _ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
+ };
+
+ return this.GetIcon(type, iconId);
+ }
+
+ ///
+ /// Get a containing the icon with the given ID, of the given type.
+ ///
+ /// 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 TexFile? GetIcon(string? type, uint iconId)
+ {
+ type ??= string.Empty;
+ if (type.Length > 0 && !type.EndsWith("/"))
+ type += "/";
+
+ var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId);
+ var file = this.GetFile(filePath);
+
+ if (type == string.Empty || file != default)
+ return file;
+
+ // Couldn't get specific type, try for generic version.
+ filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
+ file = this.GetFile(filePath);
+ return file;
+ }
+
+ ///
+ /// Get a containing the HQ icon with the given ID.
+ ///
+ /// The icon ID.
+ /// The containing the icon.
+ public TexFile? GetHqIcon(uint iconId)
+ => this.GetIcon(true, iconId);
+
+ ///
+ /// Get the passed as a drawable ImGui TextureWrap.
+ ///
+ /// The Lumina .
+ /// A that can be used to draw the texture.
+ public TextureWrap? GetImGuiTexture(TexFile? tex)
+ {
+ return tex == null ? null : Service.Get().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));
+
+ ///
+ /// Get a containing the icon with the given ID.
+ ///
+ /// The icon ID.
+ /// The containing the icon.
+ public TextureWrap? GetImGuiTextureIcon(uint iconId)
+ => this.GetImGuiTexture(this.GetIcon(iconId));
+
+ ///
+ /// Get a containing the icon with the given ID, of the given quality.
+ ///
+ /// A value indicating whether the icon should be HQ.
+ /// The icon ID.
+ /// The containing the icon.
+ public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
+ => this.GetImGuiTexture(this.GetIcon(isHq, iconId));
+
+ ///
+ /// Get a containing the icon with the given ID, of the given language.
+ ///
+ /// The requested language.
+ /// The icon ID.
+ /// The containing the icon.
+ public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
+ => this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
+
+ ///
+ /// Get a containing the icon with the given ID, of the given type.
+ ///
+ /// 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, uint iconId)
+ => this.GetImGuiTexture(this.GetIcon(type, iconId));
+
+ ///
+ /// Get a containing the HQ icon with the given ID.
+ ///
+ /// The icon ID.
+ /// The containing the icon.
+ public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
+ => this.GetImGuiTexture(this.GetHqIcon(iconId));
+
+ #endregion
+
+ ///
+ /// Dispose this DataManager.
+ ///
+ void IDisposable.Dispose()
+ {
+ this.luminaCancellationTokenSource.Cancel();
+ }
}
diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs
index a4275afcd..219b71a64 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -18,341 +18,340 @@ using Serilog.Events;
using static Dalamud.NativeFunctions;
-namespace Dalamud
+namespace Dalamud;
+
+///
+/// The main entrypoint for the Dalamud system.
+///
+public sealed class EntryPoint
{
///
- /// The main entrypoint for the Dalamud system.
+ /// Log level switch for runtime log level change.
///
- public sealed class EntryPoint
+ public static readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Verbose);
+
+ ///
+ /// A delegate used during initialization of the CLR from Dalamud.Boot.
+ ///
+ /// Pointer to a serialized data.
+ /// Event used to signal the main thread to continue.
+ public delegate void InitDelegate(IntPtr infoPtr, IntPtr mainThreadContinueEvent);
+
+ ///
+ /// A delegate used from VEH handler on exception which CoreCLR will fast fail by default.
+ ///
+ /// HGLOBAL for message.
+ public delegate IntPtr VehDelegate();
+
+ ///
+ /// Initialize Dalamud.
+ ///
+ /// Pointer to a serialized data.
+ /// Event used to signal the main thread to continue.
+ public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent)
{
- ///
- /// Log level switch for runtime log level change.
- ///
- public static readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Verbose);
+ var infoStr = Marshal.PtrToStringUTF8(infoPtr)!;
+ var info = JsonConvert.DeserializeObject(infoStr)!;
- ///
- /// A delegate used during initialization of the CLR from Dalamud.Boot.
- ///
- /// Pointer to a serialized data.
- /// Event used to signal the main thread to continue.
- public delegate void InitDelegate(IntPtr infoPtr, IntPtr mainThreadContinueEvent);
+ if ((info.BootWaitMessageBox & 4) != 0)
+ MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
- ///
- /// A delegate used from VEH handler on exception which CoreCLR will fast fail by default.
- ///
- /// HGLOBAL for message.
- public delegate IntPtr VehDelegate();
+ new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
+ }
- ///
- /// Initialize Dalamud.
- ///
- /// Pointer to a serialized data.
- /// Event used to signal the main thread to continue.
- public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent)
+ ///
+ /// Returns stack trace.
+ ///
+ /// HGlobal to wchar_t* stack trace c-string.
+ public static IntPtr VehCallback()
+ {
+ try
{
- var infoStr = Marshal.PtrToStringUTF8(infoPtr)!;
- var info = JsonConvert.DeserializeObject(infoStr)!;
-
- if ((info.BootWaitMessageBox & 4) != 0)
- MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
-
- new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
+ return Marshal.StringToHGlobalUni(Environment.StackTrace);
}
-
- ///
- /// Returns stack trace.
- ///
- /// HGlobal to wchar_t* stack trace c-string.
- public static IntPtr VehCallback()
+ catch (Exception e)
{
- try
- {
- return Marshal.StringToHGlobalUni(Environment.StackTrace);
- }
- catch (Exception e)
- {
- return Marshal.StringToHGlobalUni("Fail: " + e);
- }
+ return Marshal.StringToHGlobalUni("Fail: " + e);
}
+ }
- ///
- /// Sets up logging.
- ///
- /// Base directory.
- /// Whether to log to console.
- /// Log synchronously.
- internal static void InitLogging(string baseDirectory, bool logConsole, bool logSynchronously)
- {
+ ///
+ /// Sets up logging.
+ ///
+ /// Base directory.
+ /// Whether to log to console.
+ /// Log synchronously.
+ internal static void InitLogging(string baseDirectory, bool logConsole, bool logSynchronously)
+ {
#if DEBUG
var logPath = Path.Combine(baseDirectory, "dalamud.log");
var oldPath = Path.Combine(baseDirectory, "dalamud.old.log");
var oldPathOld = Path.Combine(baseDirectory, "dalamud.log.old");
#else
- var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
- var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.old.log");
- var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
+ var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
+ var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.old.log");
+ var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
#endif
- Log.CloseAndFlush();
+ Log.CloseAndFlush();
- var oldFileOld = new FileInfo(oldPathOld);
- if (oldFileOld.Exists)
- {
- var oldFile = new FileInfo(oldPath);
- if (oldFile.Exists)
- oldFileOld.Delete();
- else
- oldFileOld.MoveTo(oldPath);
- }
-
- CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024);
-
- var config = new LoggerConfiguration()
- .WriteTo.Sink(SerilogEventSink.Instance)
- .MinimumLevel.ControlledBy(LogLevelSwitch);
-
- if (logSynchronously)
- {
- config = config.WriteTo.File(logPath, fileSizeLimitBytes: null);
- }
- else
- {
- config = config.WriteTo.Async(a => a.File(
- logPath,
- fileSizeLimitBytes: null,
- buffered: false,
- flushToDiskInterval: TimeSpan.FromSeconds(1)));
- }
-
- if (logConsole)
- config = config.WriteTo.Console();
-
- Log.Logger = config.CreateLogger();
- }
-
- ///
- /// Initialize all Dalamud subsystems and start running on the main thread.
- ///
- /// The containing information needed to initialize Dalamud.
- /// Event used to signal the main thread to continue.
- private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
+ var oldFileOld = new FileInfo(oldPathOld);
+ if (oldFileOld.Exists)
{
- // Setup logger
- InitLogging(info.WorkingDirectory!, info.BootShowConsole, true);
- SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
-
- // Load configuration first to get some early persistent state, like log level
- var configuration = DalamudConfiguration.Load(info.ConfigurationPath!);
-
- // Set the appropriate logging level from the configuration
-#if !DEBUG
- if (!configuration.LogSynchronously)
- InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously);
- LogLevelSwitch.MinimumLevel = configuration.LogLevel;
-#endif
-
- // Log any unhandled exception.
- AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
- TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
-
- try
- {
- if (info.DelayInitializeMs > 0)
- {
- Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs));
- Thread.Sleep(info.DelayInitializeMs);
- }
-
- Log.Information(new string('-', 80));
- Log.Information("Initializing a session..");
-
- // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
- ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
-
- if (!Util.IsLinux())
- InitSymbolHandler(info);
-
- var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent);
- Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
-
- dalamud.WaitForUnload();
-
- ServiceManager.UnloadAllServices();
- }
- catch (Exception ex)
- {
- Log.Fatal(ex, "Unhandled exception on main thread.");
- }
- finally
- {
- TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
- AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
-
- Log.Information("Session has ended.");
- Log.CloseAndFlush();
- SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
- }
- }
-
- private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev)
- {
- if (ev.LogEvent.Exception == null)
- return;
-
- // Don't pass verbose/debug/info exceptions to the troubleshooter, as the developer is probably doing
- // something intentionally (or this is known).
- if (ev.LogEvent.Level < LogEventLevel.Warning)
- return;
-
- Troubleshooting.LogException(ev.LogEvent.Exception, ev.Line);
- }
-
- private static void InitSymbolHandler(DalamudStartInfo info)
- {
- try
- {
- if (string.IsNullOrEmpty(info.AssetDirectory))
- return;
-
- var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
- var searchPath = $".;{symbolPath}";
-
- // Remove any existing Symbol Handler and Init a new one with our search path added
- SymCleanup(GetCurrentProcess());
-
- if (!SymInitialize(GetCurrentProcess(), searchPath, true))
- throw new Win32Exception();
- }
- catch (Exception ex)
- {
- Log.Error(ex, "SymbolHandler Initialize Failed.");
- }
- }
-
- ///
- /// Trim existing log file to a specified length, and optionally move the excess data to another file.
- ///
- /// Target log file to trim.
- /// Maximum size of target log file.
- /// .old file to move excess data to.
- /// Maximum size of .old file.
- private static void CullLogFile(string logPath, int logMaxSize, string oldPath, int oldMaxSize)
- {
- var logFile = new FileInfo(logPath);
var oldFile = new FileInfo(oldPath);
- var targetFiles = new[]
- {
- (logFile, logMaxSize),
- (oldFile, oldMaxSize),
- };
- var buffer = new byte[4096];
-
- try
- {
- if (!logFile.Exists)
- logFile.Create().Close();
-
- // 1. Move excess data from logFile to oldFile
- if (logFile.Length > logMaxSize)
- {
- using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
-
- var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize);
- reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End);
-
- for (var i = 0; i < amountToMove; i += buffer.Length)
- writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i)));
- }
-
- // 2. Cull each of .log and .old files
- foreach (var (file, maxSize) in targetFiles)
- {
- if (!file.Exists || file.Length <= maxSize)
- continue;
-
- using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
-
- reader.Seek(file.Length - maxSize, SeekOrigin.Begin);
- for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;)
- writer.Write(buffer, 0, read);
-
- writer.SetLength(maxSize);
- }
- }
- catch (Exception ex)
- {
- if (ex is IOException)
- {
- foreach (var (file, _) in targetFiles)
- {
- try
- {
- if (file.Exists)
- file.Delete();
- }
- catch (Exception ex2)
- {
- Log.Error(ex2, "Failed to delete {file}", file.FullName);
- }
- }
- }
-
- Log.Error(ex, "Log cull failed");
-
- /*
- var caption = "XIVLauncher Error";
- var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
- _ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
- */
- }
+ if (oldFile.Exists)
+ oldFileOld.Delete();
+ else
+ oldFileOld.MoveTo(oldPath);
}
- private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
+ CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024);
+
+ var config = new LoggerConfiguration()
+ .WriteTo.Sink(SerilogEventSink.Instance)
+ .MinimumLevel.ControlledBy(LogLevelSwitch);
+
+ if (logSynchronously)
{
- switch (args.ExceptionObject)
- {
- case Exception ex:
- Log.Fatal(ex, "Unhandled exception on AppDomain");
- Troubleshooting.LogException(ex, "DalamudUnhandled");
-
- var info = "Further information could not be obtained";
- if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null)
- {
- info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}";
- }
-
- const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
- var result = MessageBoxW(
- Process.GetCurrentProcess().MainWindowHandle,
- $"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?",
- "Dalamud",
- flags);
-
- if (result == (int)User32.MessageBoxResult.IDYES)
- {
- Log.Information("User chose to disable plugins on next launch...");
- var config = Service.Get();
- config.PluginSafeMode = true;
- config.Save();
- }
-
- Log.CloseAndFlush();
- Environment.Exit(-1);
- break;
- default:
- Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
-
- Log.CloseAndFlush();
- Environment.Exit(-1);
- break;
- }
+ config = config.WriteTo.File(logPath, fileSizeLimitBytes: null);
+ }
+ else
+ {
+ config = config.WriteTo.Async(a => a.File(
+ logPath,
+ fileSizeLimitBytes: null,
+ buffered: false,
+ flushToDiskInterval: TimeSpan.FromSeconds(1)));
}
- private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
+ if (logConsole)
+ config = config.WriteTo.Console();
+
+ Log.Logger = config.CreateLogger();
+ }
+
+ ///
+ /// Initialize all Dalamud subsystems and start running on the main thread.
+ ///
+ /// The containing information needed to initialize Dalamud.
+ /// Event used to signal the main thread to continue.
+ private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
+ {
+ // Setup logger
+ InitLogging(info.WorkingDirectory!, info.BootShowConsole, true);
+ SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
+
+ // Load configuration first to get some early persistent state, like log level
+ var configuration = DalamudConfiguration.Load(info.ConfigurationPath!);
+
+ // Set the appropriate logging level from the configuration
+#if !DEBUG
+ if (!configuration.LogSynchronously)
+ InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously);
+ LogLevelSwitch.MinimumLevel = configuration.LogLevel;
+#endif
+
+ // Log any unhandled exception.
+ AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
+ TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
+
+ try
{
- if (!args.Observed)
- Log.Error(args.Exception, "Unobserved exception in Task.");
+ if (info.DelayInitializeMs > 0)
+ {
+ Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs));
+ Thread.Sleep(info.DelayInitializeMs);
+ }
+
+ Log.Information(new string('-', 80));
+ Log.Information("Initializing a session..");
+
+ // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
+ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
+
+ if (!Util.IsLinux())
+ InitSymbolHandler(info);
+
+ var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent);
+ Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
+
+ dalamud.WaitForUnload();
+
+ ServiceManager.UnloadAllServices();
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Unhandled exception on main thread.");
+ }
+ finally
+ {
+ TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
+ AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
+
+ Log.Information("Session has ended.");
+ Log.CloseAndFlush();
+ SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
}
}
+
+ private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev)
+ {
+ if (ev.LogEvent.Exception == null)
+ return;
+
+ // Don't pass verbose/debug/info exceptions to the troubleshooter, as the developer is probably doing
+ // something intentionally (or this is known).
+ if (ev.LogEvent.Level < LogEventLevel.Warning)
+ return;
+
+ Troubleshooting.LogException(ev.LogEvent.Exception, ev.Line);
+ }
+
+ private static void InitSymbolHandler(DalamudStartInfo info)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.AssetDirectory))
+ return;
+
+ var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
+ var searchPath = $".;{symbolPath}";
+
+ // Remove any existing Symbol Handler and Init a new one with our search path added
+ SymCleanup(GetCurrentProcess());
+
+ if (!SymInitialize(GetCurrentProcess(), searchPath, true))
+ throw new Win32Exception();
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "SymbolHandler Initialize Failed.");
+ }
+ }
+
+ ///
+ /// Trim existing log file to a specified length, and optionally move the excess data to another file.
+ ///
+ /// Target log file to trim.
+ /// Maximum size of target log file.
+ /// .old file to move excess data to.
+ /// Maximum size of .old file.
+ private static void CullLogFile(string logPath, int logMaxSize, string oldPath, int oldMaxSize)
+ {
+ var logFile = new FileInfo(logPath);
+ var oldFile = new FileInfo(oldPath);
+ var targetFiles = new[]
+ {
+ (logFile, logMaxSize),
+ (oldFile, oldMaxSize),
+ };
+ var buffer = new byte[4096];
+
+ try
+ {
+ if (!logFile.Exists)
+ logFile.Create().Close();
+
+ // 1. Move excess data from logFile to oldFile
+ if (logFile.Length > logMaxSize)
+ {
+ using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
+
+ var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize);
+ reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End);
+
+ for (var i = 0; i < amountToMove; i += buffer.Length)
+ writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i)));
+ }
+
+ // 2. Cull each of .log and .old files
+ foreach (var (file, maxSize) in targetFiles)
+ {
+ if (!file.Exists || file.Length <= maxSize)
+ continue;
+
+ using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
+
+ reader.Seek(file.Length - maxSize, SeekOrigin.Begin);
+ for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;)
+ writer.Write(buffer, 0, read);
+
+ writer.SetLength(maxSize);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (ex is IOException)
+ {
+ foreach (var (file, _) in targetFiles)
+ {
+ try
+ {
+ if (file.Exists)
+ file.Delete();
+ }
+ catch (Exception ex2)
+ {
+ Log.Error(ex2, "Failed to delete {file}", file.FullName);
+ }
+ }
+ }
+
+ Log.Error(ex, "Log cull failed");
+
+ /*
+ var caption = "XIVLauncher Error";
+ var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
+ _ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
+ */
+ }
+ }
+
+ private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
+ {
+ switch (args.ExceptionObject)
+ {
+ case Exception ex:
+ Log.Fatal(ex, "Unhandled exception on AppDomain");
+ Troubleshooting.LogException(ex, "DalamudUnhandled");
+
+ var info = "Further information could not be obtained";
+ if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null)
+ {
+ info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}";
+ }
+
+ const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
+ var result = MessageBoxW(
+ Process.GetCurrentProcess().MainWindowHandle,
+ $"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?",
+ "Dalamud",
+ flags);
+
+ if (result == (int)User32.MessageBoxResult.IDYES)
+ {
+ Log.Information("User chose to disable plugins on next launch...");
+ var config = Service.Get();
+ config.PluginSafeMode = true;
+ config.Save();
+ }
+
+ Log.CloseAndFlush();
+ Environment.Exit(-1);
+ break;
+ default:
+ Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
+
+ Log.CloseAndFlush();
+ Environment.Exit(-1);
+ break;
+ }
+ }
+
+ private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
+ {
+ if (!args.Observed)
+ Log.Error(args.Exception, "Unobserved exception in Task.");
+ }
}
diff --git a/Dalamud/Game/BaseAddressResolver.cs b/Dalamud/Game/BaseAddressResolver.cs
index 81449bb07..24e7dffe8 100644
--- a/Dalamud/Game/BaseAddressResolver.cs
+++ b/Dalamud/Game/BaseAddressResolver.cs
@@ -5,114 +5,113 @@ using System.Runtime.InteropServices;
using JetBrains.Annotations;
-namespace Dalamud.Game
+namespace Dalamud.Game;
+
+///
+/// Base memory address resolver.
+///
+public abstract class BaseAddressResolver
{
///
- /// Base memory address resolver.
+ /// Gets a list of memory addresses that were found, to list in /xldata.
///
- public abstract class BaseAddressResolver
+ public static Dictionary> DebugScannedValues { get; } = new();
+
+ ///
+ /// Gets or sets a value indicating whether the resolver has successfully run or .
+ ///
+ protected bool IsResolved { get; set; }
+
+ ///
+ /// Setup the resolver, calling the appropriate method based on the process architecture,
+ /// using the default SigScanner.
+ ///
+ /// For plugins. Not intended to be called from Dalamud Service{T} constructors.
+ ///
+ [UsedImplicitly]
+ public void Setup() => this.Setup(Service.Get());
+
+ ///
+ /// Setup the resolver, calling the appropriate method based on the process architecture.
+ ///
+ /// The SigScanner instance.
+ public void Setup(SigScanner scanner)
{
- ///
- /// Gets a list of memory addresses that were found, to list in /xldata.
- ///
- public static Dictionary> DebugScannedValues { get; } = new();
+ // Because C# don't allow to call virtual function while in ctor
+ // we have to do this shit :\
- ///
- /// Gets or sets a value indicating whether the resolver has successfully run or .
- ///
- protected bool IsResolved { get; set; }
-
- ///
- /// Setup the resolver, calling the appropriate method based on the process architecture,
- /// using the default SigScanner.
- ///
- /// For plugins. Not intended to be called from Dalamud Service{T} constructors.
- ///
- [UsedImplicitly]
- public void Setup() => this.Setup(Service.Get());
-
- ///
- /// Setup the resolver, calling the appropriate method based on the process architecture.
- ///
- /// The SigScanner instance.
- public void Setup(SigScanner scanner)
+ if (this.IsResolved)
{
- // Because C# don't allow to call virtual function while in ctor
- // we have to do this shit :\
-
- if (this.IsResolved)
- {
- return;
- }
-
- if (scanner.Is32BitProcess)
- {
- this.Setup32Bit(scanner);
- }
- else
- {
- this.Setup64Bit(scanner);
- }
-
- this.SetupInternal(scanner);
-
- var className = this.GetType().Name;
- var list = new List<(string, IntPtr)>();
- lock (DebugScannedValues)
- DebugScannedValues[className] = list;
-
- foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
- {
- list.Add((property.Name, (IntPtr)property.GetValue(this)));
- }
-
- this.IsResolved = true;
+ return;
}
- ///
- /// 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
+ if (scanner.Is32BitProcess)
{
- // 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);
+ this.Setup32Bit(scanner);
+ }
+ else
+ {
+ this.Setup64Bit(scanner);
}
- ///
- /// Setup the resolver by finding any necessary memory addresses.
- ///
- /// The SigScanner instance.
- protected virtual void Setup32Bit(SigScanner scanner)
+ this.SetupInternal(scanner);
+
+ var className = this.GetType().Name;
+ var list = new List<(string, IntPtr)>();
+ lock (DebugScannedValues)
+ DebugScannedValues[className] = list;
+
+ foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
{
- throw new NotSupportedException("32 bit version is not supported.");
+ list.Add((property.Name, (IntPtr)property.GetValue(this)));
}
- ///
- /// 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.");
- }
+ this.IsResolved = true;
+ }
- ///
- /// Setup the resolver by finding any necessary memory addresses.
- ///
- /// The SigScanner instance.
- protected virtual void SetupInternal(SigScanner scanner)
- {
- // Do nothing
- }
+ ///
+ /// 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.");
+ }
+
+ ///
+ /// 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.");
+ }
+
+ ///
+ /// 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/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs
index 3e489af82..03e28073a 100644
--- a/Dalamud/Game/ChatHandlers.cs
+++ b/Dalamud/Game/ChatHandlers.cs
@@ -20,314 +20,313 @@ using Dalamud.Plugin.Internal;
using Dalamud.Utility;
using Serilog;
-namespace Dalamud.Game
+namespace Dalamud.Game;
+
+///
+/// Chat events and public helper functions.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public class ChatHandlers : IServiceType
{
- ///
- /// Chat events and public helper functions.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public class ChatHandlers : IServiceType
+ // private static readonly Dictionary UnicodeToDiscordEmojiDict = new()
+ // {
+ // { "", "<:ffxive071:585847382210642069>" },
+ // { "", "<:ffxive083:585848592699490329>" },
+ // };
+
+ // 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(
+ @"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|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%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ private readonly Dictionary retainerSaleRegexes = new()
{
- // private static readonly Dictionary UnicodeToDiscordEmojiDict = new()
- // {
- // { "", "<:ffxive071:585847382210642069>" },
- // { "", "<:ffxive083:585848592699490329>" },
- // };
-
- // 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(
- @"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|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%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
- RegexOptions.Compiled | RegexOptions.IgnoreCase);
-
- private readonly Dictionary retainerSaleRegexes = new()
{
+ ClientLanguage.Japanese,
+ new Regex[]
{
- 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),
- }
- },
- {
- 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),
- }
- },
- {
- ClientLanguage.French,
- new Regex[]
- {
- new Regex(@"^Un servant a vendu (?
- .+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
- }
- },
- };
-
- private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
-
- private readonly DalamudLinkPayload openInstallerWindowLink;
-
- [ServiceManager.ServiceDependency]
- private readonly DalamudConfiguration configuration = Service.Get();
-
- private bool hasSeenLoadingMsg;
- private bool hasAutoUpdatedPlugins;
-
- [ServiceManager.ServiceConstructor]
- private ChatHandlers(ChatGui chatGui)
- {
- chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
- chatGui.ChatMessage += this.OnChatMessage;
-
- this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
- {
- Service.GetNullable()?.OpenPluginInstaller();
- });
- }
-
- ///
- /// Gets the last URL seen in chat.
- ///
- public string? LastLink { get; private set; }
-
- ///
- /// Convert a TextPayload to SeString and wrap in italics payloads.
- ///
- /// Text to convert.
- /// SeString payload of italicized text.
- public static SeString MakeItalics(string text)
- => MakeItalics(new TextPayload(text));
-
- ///
- /// Convert a TextPayload to SeString and wrap in italics payloads.
- ///
- /// Text to convert.
- /// SeString payload of italicized text.
- public static SeString MakeItalics(TextPayload text)
- => new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff);
-
- private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
- {
- var textVal = message.TextValue;
-
- if (!this.configuration.DisableRmtFiltering)
- {
- var matched = this.rmtRegex.IsMatch(textVal);
- if (matched)
- {
- // This seems to be a RMT ad - let's not show it
- Log.Debug("Handled RMT ad: " + message.TextValue);
- isHandled = true;
- return;
- }
+ new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?
- .*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
+ new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?
- .*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
}
-
- if (this.configuration.BadWords != null &&
- this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
+ },
+ {
+ ClientLanguage.English,
+ new Regex[]
{
- // This seems to be in the user block list - let's not show it
- Log.Debug("Blocklist triggered");
+ 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),
+ }
+ },
+ {
+ ClientLanguage.French,
+ new Regex[]
+ {
+ new Regex(@"^Un servant a vendu (?
- .+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
+ }
+ },
+ };
+
+ private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
+
+ private readonly DalamudLinkPayload openInstallerWindowLink;
+
+ [ServiceManager.ServiceDependency]
+ private readonly DalamudConfiguration configuration = Service.Get();
+
+ private bool hasSeenLoadingMsg;
+ private bool hasAutoUpdatedPlugins;
+
+ [ServiceManager.ServiceConstructor]
+ private ChatHandlers(ChatGui chatGui)
+ {
+ chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
+ chatGui.ChatMessage += this.OnChatMessage;
+
+ this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
+ {
+ Service.GetNullable()?.OpenPluginInstaller();
+ });
+ }
+
+ ///
+ /// Gets the last URL seen in chat.
+ ///
+ public string? LastLink { get; private set; }
+
+ ///
+ /// Convert a TextPayload to SeString and wrap in italics payloads.
+ ///
+ /// Text to convert.
+ /// SeString payload of italicized text.
+ public static SeString MakeItalics(string text)
+ => MakeItalics(new TextPayload(text));
+
+ ///
+ /// Convert a TextPayload to SeString and wrap in italics payloads.
+ ///
+ /// Text to convert.
+ /// SeString payload of italicized text.
+ public static SeString MakeItalics(TextPayload text)
+ => new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff);
+
+ private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
+ {
+ var textVal = message.TextValue;
+
+ if (!this.configuration.DisableRmtFiltering)
+ {
+ var matched = this.rmtRegex.IsMatch(textVal);
+ if (matched)
+ {
+ // This seems to be a RMT ad - let's not show it
+ Log.Debug("Handled RMT ad: " + message.TextValue);
isHandled = true;
return;
}
}
- private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
+ if (this.configuration.BadWords != null &&
+ this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
{
- var startInfo = Service.Get();
- var clientState = Service.GetNullable();
- if (clientState == null)
- return;
+ // This seems to be in the user block list - let's not show it
+ Log.Debug("Blocklist triggered");
+ isHandled = true;
+ return;
+ }
+ }
- if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
- this.PrintWelcomeMessage();
+ private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
+ {
+ var startInfo = Service.Get();
+ var clientState = Service.GetNullable();
+ if (clientState == null)
+ return;
- // For injections while logged in
- if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
- this.PrintWelcomeMessage();
+ if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
+ this.PrintWelcomeMessage();
- if (!this.hasAutoUpdatedPlugins)
- this.AutoUpdatePlugins();
+ // For injections while logged in
+ if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
+ this.PrintWelcomeMessage();
+
+ if (!this.hasAutoUpdatedPlugins)
+ this.AutoUpdatePlugins();
#if !DEBUG && false
if (!this.hasSeenLoadingMsg)
return;
#endif
- if (type == XivChatType.RetainerSale)
+ if (type == XivChatType.RetainerSale)
+ {
+ foreach (var regex in this.retainerSaleRegexes[startInfo.Language])
{
- foreach (var regex in this.retainerSaleRegexes[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
+ // but we'd be checking the main match anyway
+ var itemInfo = matchInfo.Groups["item"];
+ if (!itemInfo.Success)
+ continue;
+
+ var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
+ if (itemLink == default)
{
- 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
- // but we'd be checking the main match anyway
- var itemInfo = matchInfo.Groups["item"];
- if (!itemInfo.Success)
- continue;
-
- 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;
- }
-
- Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}");
-
- 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(",", string.Empty).Replace(".", string.Empty), out var itemValue))
- continue;
-
- // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
+ Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
break;
}
+
+ Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}");
+
+ 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(",", string.Empty).Replace(".", string.Empty), out var itemValue))
+ continue;
+
+ // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
+ break;
}
-
- var messageCopy = message;
- var senderCopy = sender;
-
- var linkMatch = this.urlRegex.Match(message.TextValue);
- if (linkMatch.Value.Length > 0)
- this.LastLink = linkMatch.Value;
}
- private void PrintWelcomeMessage()
+ var messageCopy = message;
+ var senderCopy = sender;
+
+ var linkMatch = this.urlRegex.Match(message.TextValue);
+ if (linkMatch.Value.Length > 0)
+ this.LastLink = linkMatch.Value;
+ }
+
+ private void PrintWelcomeMessage()
+ {
+ var chatGui = Service.GetNullable();
+ var pluginManager = Service.GetNullable();
+ var dalamudInterface = Service.GetNullable();
+
+ if (chatGui == null || pluginManager == null || dalamudInterface == null)
+ return;
+
+ var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
+
+ chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
+ + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
+
+ if (this.configuration.PrintPluginsWelcomeMsg)
{
- var chatGui = Service.GetNullable();
- var pluginManager = Service.GetNullable();
- var dalamudInterface = Service.GetNullable();
-
- if (chatGui == null || pluginManager == null || dalamudInterface == null)
- return;
-
- var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
-
- chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
- + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
-
- if (this.configuration.PrintPluginsWelcomeMsg)
+ foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
{
- foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
- {
- chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion));
- }
+ chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion));
}
-
- if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
- {
- chatGui.PrintChat(new XivChatEntry
- {
- Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."),
- Type = XivChatType.Notice,
- });
-
- if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
- {
- dalamudInterface.OpenChangelogWindow();
- this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
- }
-
- this.configuration.LastVersion = assemblyVersion;
- this.configuration.Save();
- }
-
- this.hasSeenLoadingMsg = true;
}
- private void AutoUpdatePlugins()
+ if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
{
- var chatGui = Service.GetNullable();
- var pluginManager = Service.GetNullable();
- var notifications = Service.GetNullable();
-
- if (chatGui == null || pluginManager == null || notifications == null)
- return;
-
- if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
+ chatGui.PrintChat(new XivChatEntry
{
- // Plugins aren't ready yet.
- return;
- }
-
- this.hasAutoUpdatedPlugins = true;
-
- Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
- {
- if (task.IsFaulted)
- {
- Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
- return;
- }
-
- var updatedPlugins = task.Result;
- if (updatedPlugins.Any())
- {
- if (this.configuration.AutoUpdatePlugins)
- {
- Service.Get().PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
- notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info);
- }
- else
- {
- chatGui.PrintChat(new XivChatEntry
- {
- Message = 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(500),
- this.openInstallerWindowLink,
- new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")),
- RawPayload.LinkTerminator,
- new UIForegroundPayload(0),
- new TextPayload("]"),
- }),
- Type = XivChatType.Urgent,
- });
- }
- }
+ Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."),
+ Type = XivChatType.Notice,
});
+
+ if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
+ {
+ dalamudInterface.OpenChangelogWindow();
+ this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
+ }
+
+ this.configuration.LastVersion = assemblyVersion;
+ this.configuration.Save();
}
+
+ this.hasSeenLoadingMsg = true;
+ }
+
+ private void AutoUpdatePlugins()
+ {
+ var chatGui = Service.GetNullable();
+ var pluginManager = Service.GetNullable();
+ var notifications = Service.GetNullable();
+
+ if (chatGui == null || pluginManager == null || notifications == null)
+ return;
+
+ if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
+ {
+ // Plugins aren't ready yet.
+ return;
+ }
+
+ this.hasAutoUpdatedPlugins = true;
+
+ Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
+ {
+ if (task.IsFaulted)
+ {
+ Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
+ return;
+ }
+
+ var updatedPlugins = task.Result;
+ if (updatedPlugins.Any())
+ {
+ if (this.configuration.AutoUpdatePlugins)
+ {
+ Service.Get().PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
+ notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info);
+ }
+ else
+ {
+ chatGui.PrintChat(new XivChatEntry
+ {
+ Message = 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(500),
+ this.openInstallerWindowLink,
+ new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")),
+ RawPayload.LinkTerminator,
+ new UIForegroundPayload(0),
+ new TextPayload("]"),
+ }),
+ Type = XivChatType.Urgent,
+ });
+ }
+ }
+ });
}
}
diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs
index 9ada955f2..8113e0593 100644
--- a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs
+++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs
@@ -1,72 +1,71 @@
using Dalamud.Game.ClientState.Resolvers;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
-namespace Dalamud.Game.ClientState.Aetherytes
+namespace Dalamud.Game.ClientState.Aetherytes;
+
+///
+/// This class represents an entry in the Aetheryte list.
+///
+public sealed class AetheryteEntry
{
+ private readonly TeleportInfo data;
+
///
- /// This class represents an entry in the Aetheryte list.
+ /// Initializes a new instance of the class.
///
- public sealed class AetheryteEntry
+ /// Data read from the Aetheryte List.
+ internal AetheryteEntry(TeleportInfo data)
{
- private readonly TeleportInfo data;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Data read from the Aetheryte List.
- internal AetheryteEntry(TeleportInfo data)
- {
- this.data = data;
- }
-
- ///
- /// Gets the Aetheryte ID.
- ///
- public uint AetheryteId => this.data.AetheryteId;
-
- ///
- /// Gets the Territory ID.
- ///
- public uint TerritoryId => this.data.TerritoryId;
-
- ///
- /// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.).
- ///
- public byte SubIndex => this.data.SubIndex;
-
- ///
- /// Gets the Ward. Zero if not a Shared Estate.
- ///
- public byte Ward => this.data.Ward;
-
- ///
- /// Gets the Plot. Zero if not a Shared Estate.
- ///
- public byte Plot => this.data.Plot;
-
- ///
- /// Gets the Cost in Gil to Teleport to this location.
- ///
- public uint GilCost => this.data.GilCost;
-
- ///
- /// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not.
- ///
- public bool IsFavourite => this.data.IsFavourite != 0;
-
- ///
- /// Gets a value indicating whether this Aetheryte is a Shared Estate or not.
- ///
- public bool IsSharedHouse => this.data.IsSharedHouse;
-
- ///
- /// Gets a value indicating whether this Aetheryte is an Appartment or not.
- ///
- public bool IsAppartment => this.data.IsAppartment;
-
- ///
- /// Gets the Aetheryte data related to this aetheryte.
- ///
- public ExcelResolver AetheryteData => new(this.AetheryteId);
+ this.data = data;
}
+
+ ///
+ /// Gets the Aetheryte ID.
+ ///
+ public uint AetheryteId => this.data.AetheryteId;
+
+ ///
+ /// Gets the Territory ID.
+ ///
+ public uint TerritoryId => this.data.TerritoryId;
+
+ ///
+ /// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.).
+ ///
+ public byte SubIndex => this.data.SubIndex;
+
+ ///
+ /// Gets the Ward. Zero if not a Shared Estate.
+ ///
+ public byte Ward => this.data.Ward;
+
+ ///
+ /// Gets the Plot. Zero if not a Shared Estate.
+ ///
+ public byte Plot => this.data.Plot;
+
+ ///
+ /// Gets the Cost in Gil to Teleport to this location.
+ ///
+ public uint GilCost => this.data.GilCost;
+
+ ///
+ /// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not.
+ ///
+ public bool IsFavourite => this.data.IsFavourite != 0;
+
+ ///
+ /// Gets a value indicating whether this Aetheryte is a Shared Estate or not.
+ ///
+ public bool IsSharedHouse => this.data.IsSharedHouse;
+
+ ///
+ /// Gets a value indicating whether this Aetheryte is an Appartment or not.
+ ///
+ public bool IsAppartment => this.data.IsAppartment;
+
+ ///
+ /// Gets the Aetheryte data related to this aetheryte.
+ ///
+ public ExcelResolver AetheryteData => new(this.AetheryteId);
}
diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs
index dd735bd42..46b285c68 100644
--- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs
+++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs
@@ -7,105 +7,104 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Serilog;
-namespace Dalamud.Game.ClientState.Aetherytes
+namespace Dalamud.Game.ClientState.Aetherytes;
+
+///
+/// This collection represents the list of available Aetherytes in the Teleport window.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public sealed partial class AetheryteList : IServiceType
{
- ///
- /// This collection represents the list of available Aetherytes in the Teleport window.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public sealed partial class AetheryteList : IServiceType
+ [ServiceManager.ServiceDependency]
+ private readonly ClientState clientState = Service.Get();
+ private readonly ClientStateAddressResolver address;
+ private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
+
+ [ServiceManager.ServiceConstructor]
+ private AetheryteList()
{
- [ServiceManager.ServiceDependency]
- private readonly ClientState clientState = Service.Get();
- private readonly ClientStateAddressResolver address;
- private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
+ this.address = this.clientState.AddressResolver;
+ this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer(this.address.UpdateAetheryteList);
- [ServiceManager.ServiceConstructor]
- private AetheryteList()
+ Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
+ }
+
+ private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1);
+
+ ///
+ /// Gets the amount of Aetherytes the local player has unlocked.
+ ///
+ public unsafe int Length
+ {
+ get
{
- this.address = this.clientState.AddressResolver;
- this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer(this.address.UpdateAetheryteList);
-
- Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
- }
-
- private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1);
-
- ///
- /// Gets the amount of Aetherytes the local player has unlocked.
- ///
- public unsafe int Length
- {
- get
- {
- if (this.clientState.LocalPlayer == null)
- return 0;
-
- this.Update();
-
- if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last)
- return 0;
-
- return (int)TelepoStruct->TeleportList.Size();
- }
- }
-
- private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo;
-
- ///
- /// Gets a Aetheryte Entry at the specified index.
- ///
- /// Index.
- /// A at the specified index.
- public unsafe AetheryteEntry? this[int index]
- {
- get
- {
- if (index < 0 || index >= this.Length)
- {
- return null;
- }
-
- if (this.clientState.LocalPlayer == null)
- return null;
-
- return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
- }
- }
-
- private void Update()
- {
- // this is very very important as otherwise it crashes
if (this.clientState.LocalPlayer == null)
- return;
+ return 0;
- this.updateAetheryteListFunc(this.address.Telepo, 0);
+ this.Update();
+
+ if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last)
+ return 0;
+
+ return (int)TelepoStruct->TeleportList.Size();
}
}
+ private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo;
+
///
- /// This collection represents the list of available Aetherytes in the Teleport window.
+ /// Gets a Aetheryte Entry at the specified index.
///
- public sealed partial class AetheryteList : IReadOnlyCollection
+ /// Index.
+ /// A at the specified index.
+ public unsafe AetheryteEntry? this[int index]
{
- ///
- public int Count => this.Length;
-
- ///
- public IEnumerator GetEnumerator()
+ get
{
- for (var i = 0; i < this.Length; i++)
+ if (index < 0 || index >= this.Length)
{
- yield return this[i];
+ return null;
}
- }
- ///
- IEnumerator IEnumerable.GetEnumerator()
- {
- return this.GetEnumerator();
+ if (this.clientState.LocalPlayer == null)
+ return null;
+
+ return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
}
}
+
+ private void Update()
+ {
+ // this is very very important as otherwise it crashes
+ if (this.clientState.LocalPlayer == null)
+ return;
+
+ this.updateAetheryteListFunc(this.address.Telepo, 0);
+ }
+}
+
+///
+/// This collection represents the list of available Aetherytes in the Teleport window.
+///
+public sealed partial class AetheryteList : IReadOnlyCollection
+{
+ ///
+ public int Count => this.Length;
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ for (var i = 0; i < this.Length; i++)
+ {
+ yield return this[i];
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
}
diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs
index a345d27e2..0566a1f76 100644
--- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs
+++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs
@@ -7,179 +7,178 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Serilog;
-namespace Dalamud.Game.ClientState.Buddy
+namespace Dalamud.Game.ClientState.Buddy;
+
+///
+/// This collection represents the buddies present in your squadron or trust party.
+/// It does not include the local player.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public sealed partial class BuddyList : IServiceType
{
- ///
- /// This collection represents the buddies present in your squadron or trust party.
- /// It does not include the local player.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public sealed partial class BuddyList : IServiceType
+ private const uint InvalidObjectID = 0xE0000000;
+
+ [ServiceManager.ServiceDependency]
+ private readonly ClientState clientState = Service.Get();
+
+ private readonly ClientStateAddressResolver address;
+
+ [ServiceManager.ServiceConstructor]
+ private BuddyList()
{
- private const uint InvalidObjectID = 0xE0000000;
+ this.address = this.clientState.AddressResolver;
- [ServiceManager.ServiceDependency]
- private readonly ClientState clientState = Service.Get();
+ Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
+ }
- private readonly ClientStateAddressResolver address;
-
- [ServiceManager.ServiceConstructor]
- private BuddyList()
+ ///
+ /// Gets the amount of battle buddies the local player has.
+ ///
+ public int Length
+ {
+ get
{
- this.address = this.clientState.AddressResolver;
-
- Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
- }
-
- ///
- /// Gets the amount of battle buddies the local player has.
- ///
- public int Length
- {
- get
+ var i = 0;
+ for (; i < 3; i++)
{
- var i = 0;
- for (; i < 3; i++)
- {
- var addr = this.GetBattleBuddyMemberAddress(i);
- var member = this.CreateBuddyMemberReference(addr);
- if (member == null)
- break;
- }
-
- return i;
+ var addr = this.GetBattleBuddyMemberAddress(i);
+ var member = this.CreateBuddyMemberReference(addr);
+ if (member == null)
+ break;
}
- }
- ///
- /// Gets a value indicating whether the local player's companion is present.
- ///
- public bool CompanionBuddyPresent => this.CompanionBuddy != null;
-
- ///
- /// Gets a value indicating whether the local player's pet is present.
- ///
- public bool PetBuddyPresent => this.PetBuddy != null;
-
- ///
- /// Gets the active companion buddy.
- ///
- public BuddyMember? CompanionBuddy
- {
- get
- {
- var addr = this.GetCompanionBuddyMemberAddress();
- return this.CreateBuddyMemberReference(addr);
- }
- }
-
- ///
- /// Gets the active pet buddy.
- ///
- public BuddyMember? PetBuddy
- {
- get
- {
- var addr = this.GetPetBuddyMemberAddress();
- return this.CreateBuddyMemberReference(addr);
- }
- }
-
- ///
- /// Gets the address of the buddy list.
- ///
- internal IntPtr BuddyListAddress => this.address.BuddyList;
-
- private static int BuddyMemberSize { get; } = Marshal.SizeOf();
-
- private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
-
- ///
- /// Gets a battle buddy at the specified spawn index.
- ///
- /// Spawn index.
- /// A at the specified spawn index.
- public BuddyMember? this[int index]
- {
- get
- {
- var address = this.GetBattleBuddyMemberAddress(index);
- return this.CreateBuddyMemberReference(address);
- }
- }
-
- ///
- /// Gets the address of the companion buddy.
- ///
- /// The memory address of the companion buddy.
- public unsafe IntPtr GetCompanionBuddyMemberAddress()
- {
- return (IntPtr)(&this.BuddyListStruct->Companion);
- }
-
- ///
- /// Gets the address of the pet buddy.
- ///
- /// The memory address of the pet buddy.
- public unsafe IntPtr GetPetBuddyMemberAddress()
- {
- return (IntPtr)(&this.BuddyListStruct->Pet);
- }
-
- ///
- /// Gets the address of the battle buddy at the specified index of the buddy list.
- ///
- /// The index of the battle buddy.
- /// The memory address of the battle buddy.
- public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
- {
- if (index < 0 || index >= 3)
- return IntPtr.Zero;
-
- return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize));
- }
-
- ///
- /// Create a reference to a buddy.
- ///
- /// The address of the buddy in memory.
- /// object containing the requested data.
- public BuddyMember? CreateBuddyMemberReference(IntPtr address)
- {
- if (this.clientState.LocalContentId == 0)
- return null;
-
- if (address == IntPtr.Zero)
- return null;
-
- var buddy = new BuddyMember(address);
- if (buddy.ObjectId == InvalidObjectID)
- return null;
-
- return buddy;
+ return i;
}
}
///
- /// This collection represents the buddies present in your squadron or trust party.
+ /// Gets a value indicating whether the local player's companion is present.
///
- public sealed partial class BuddyList : IReadOnlyCollection
+ public bool CompanionBuddyPresent => this.CompanionBuddy != null;
+
+ ///
+ /// Gets a value indicating whether the local player's pet is present.
+ ///
+ public bool PetBuddyPresent => this.PetBuddy != null;
+
+ ///
+ /// Gets the active companion buddy.
+ ///
+ public BuddyMember? CompanionBuddy
{
- ///
- int IReadOnlyCollection.Count => this.Length;
-
- ///
- public IEnumerator GetEnumerator()
+ get
{
- for (var i = 0; i < this.Length; i++)
- {
- yield return this[i];
- }
+ var addr = this.GetCompanionBuddyMemberAddress();
+ return this.CreateBuddyMemberReference(addr);
}
+ }
- ///
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ ///
+ /// Gets the active pet buddy.
+ ///
+ public BuddyMember? PetBuddy
+ {
+ get
+ {
+ var addr = this.GetPetBuddyMemberAddress();
+ return this.CreateBuddyMemberReference(addr);
+ }
+ }
+
+ ///
+ /// Gets the address of the buddy list.
+ ///
+ internal IntPtr BuddyListAddress => this.address.BuddyList;
+
+ private static int BuddyMemberSize { get; } = Marshal.SizeOf();
+
+ private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
+
+ ///
+ /// Gets a battle buddy at the specified spawn index.
+ ///
+ /// Spawn index.
+ /// A at the specified spawn index.
+ public BuddyMember? this[int index]
+ {
+ get
+ {
+ var address = this.GetBattleBuddyMemberAddress(index);
+ return this.CreateBuddyMemberReference(address);
+ }
+ }
+
+ ///
+ /// Gets the address of the companion buddy.
+ ///
+ /// The memory address of the companion buddy.
+ public unsafe IntPtr GetCompanionBuddyMemberAddress()
+ {
+ return (IntPtr)(&this.BuddyListStruct->Companion);
+ }
+
+ ///
+ /// Gets the address of the pet buddy.
+ ///
+ /// The memory address of the pet buddy.
+ public unsafe IntPtr GetPetBuddyMemberAddress()
+ {
+ return (IntPtr)(&this.BuddyListStruct->Pet);
+ }
+
+ ///
+ /// Gets the address of the battle buddy at the specified index of the buddy list.
+ ///
+ /// The index of the battle buddy.
+ /// The memory address of the battle buddy.
+ public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
+ {
+ if (index < 0 || index >= 3)
+ return IntPtr.Zero;
+
+ return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize));
+ }
+
+ ///
+ /// Create a reference to a buddy.
+ ///
+ /// The address of the buddy in memory.
+ /// object containing the requested data.
+ public BuddyMember? CreateBuddyMemberReference(IntPtr address)
+ {
+ if (this.clientState.LocalContentId == 0)
+ return null;
+
+ if (address == IntPtr.Zero)
+ return null;
+
+ var buddy = new BuddyMember(address);
+ if (buddy.ObjectId == InvalidObjectID)
+ return null;
+
+ return buddy;
}
}
+
+///
+/// This collection represents the buddies present in your squadron or trust party.
+///
+public sealed partial class BuddyList : IReadOnlyCollection
+{
+ ///
+ int IReadOnlyCollection.Count => this.Length;
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ for (var i = 0; i < this.Length; i++)
+ {
+ yield return this[i];
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+}
diff --git a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs
index 4cad665e1..80a510ce5 100644
--- a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs
+++ b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs
@@ -4,73 +4,72 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers;
-namespace Dalamud.Game.ClientState.Buddy
+namespace Dalamud.Game.ClientState.Buddy;
+
+///
+/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
+///
+public unsafe class BuddyMember
{
+ [ServiceManager.ServiceDependency]
+ private readonly ObjectTable objectTable = Service.Get();
+
///
- /// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
+ /// Initializes a new instance of the class.
///
- public unsafe class BuddyMember
+ /// Buddy address.
+ internal BuddyMember(IntPtr address)
{
- [ServiceManager.ServiceDependency]
- private readonly ObjectTable objectTable = Service.Get();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Buddy address.
- internal BuddyMember(IntPtr address)
- {
- this.Address = address;
- }
-
- ///
- /// Gets the address of the buddy in memory.
- ///
- public IntPtr Address { get; }
-
- ///
- /// Gets the object ID of this buddy.
- ///
- public uint ObjectId => this.Struct->ObjectID;
-
- ///
- /// Gets the actor associated with this buddy.
- ///
- ///
- /// This iterates the actor table, it should be used with care.
- ///
- public GameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
-
- ///
- /// Gets the current health of this buddy.
- ///
- public uint CurrentHP => this.Struct->CurrentHealth;
-
- ///
- /// Gets the maximum health of this buddy.
- ///
- public uint MaxHP => this.Struct->MaxHealth;
-
- ///
- /// Gets the data ID of this buddy.
- ///
- public uint DataID => this.Struct->DataID;
-
- ///
- /// Gets the Mount data related to this buddy. It should only be used with companion buddies.
- ///
- public ExcelResolver MountData => new(this.DataID);
-
- ///
- /// Gets the Pet data related to this buddy. It should only be used with pet buddies.
- ///
- public ExcelResolver PetData => new(this.DataID);
-
- ///
- /// Gets the Trust data related to this buddy. It should only be used with battle buddies.
- ///
- public ExcelResolver TrustData => new(this.DataID);
-
- private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
+ this.Address = address;
}
+
+ ///
+ /// Gets the address of the buddy in memory.
+ ///
+ public IntPtr Address { get; }
+
+ ///
+ /// Gets the object ID of this buddy.
+ ///
+ public uint ObjectId => this.Struct->ObjectID;
+
+ ///
+ /// Gets the actor associated with this buddy.
+ ///
+ ///
+ /// This iterates the actor table, it should be used with care.
+ ///
+ public GameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
+
+ ///
+ /// Gets the current health of this buddy.
+ ///
+ public uint CurrentHP => this.Struct->CurrentHealth;
+
+ ///
+ /// Gets the maximum health of this buddy.
+ ///
+ public uint MaxHP => this.Struct->MaxHealth;
+
+ ///
+ /// Gets the data ID of this buddy.
+ ///
+ public uint DataID => this.Struct->DataID;
+
+ ///
+ /// Gets the Mount data related to this buddy. It should only be used with companion buddies.
+ ///
+ public ExcelResolver MountData => new(this.DataID);
+
+ ///
+ /// Gets the Pet data related to this buddy. It should only be used with pet buddies.
+ ///
+ public ExcelResolver PetData => new(this.DataID);
+
+ ///
+ /// Gets the Trust data related to this buddy. It should only be used with battle buddies.
+ ///
+ public ExcelResolver TrustData => new(this.DataID);
+
+ private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
}
diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs
index b58da8ad5..491d2aeae 100644
--- a/Dalamud/Game/ClientState/ClientState.cs
+++ b/Dalamud/Game/ClientState/ClientState.cs
@@ -13,193 +13,192 @@ using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using Serilog;
-namespace Dalamud.Game.ClientState
+namespace Dalamud.Game.ClientState;
+
+///
+/// This class represents the state of the game client at the time of access.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public sealed class ClientState : IDisposable, IServiceType
{
- ///
- /// This class represents the state of the game client at the time of access.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public sealed class ClientState : IDisposable, IServiceType
+ private readonly ClientStateAddressResolver address;
+ private readonly Hook setupTerritoryTypeHook;
+
+ [ServiceManager.ServiceDependency]
+ private readonly Framework framework = Service.Get();
+
+ [ServiceManager.ServiceDependency]
+ private readonly NetworkHandlers networkHandlers = Service.Get();
+
+ private bool lastConditionNone = true;
+ private bool lastFramePvP = false;
+
+ [ServiceManager.ServiceConstructor]
+ private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo)
{
- private readonly ClientStateAddressResolver address;
- private readonly Hook setupTerritoryTypeHook;
+ this.address = new ClientStateAddressResolver();
+ this.address.Setup(sigScanner);
- [ServiceManager.ServiceDependency]
- private readonly Framework framework = Service.Get();
+ Log.Verbose("===== C L I E N T S T A T E =====");
- [ServiceManager.ServiceDependency]
- private readonly NetworkHandlers networkHandlers = Service.Get();
+ this.ClientLanguage = startInfo.Language;
- private bool lastConditionNone = true;
- private bool lastFramePvP = false;
+ Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
- [ServiceManager.ServiceConstructor]
- private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo)
+ this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
+
+ this.framework.Update += this.FrameworkOnOnUpdateEvent;
+
+ this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
+
+ ///
+ /// Event that gets fired when the current Territory changes.
+ ///
+ public event EventHandler TerritoryChanged;
+
+ ///
+ /// Event that fires when a character is logging in.
+ ///
+ public event EventHandler Login;
+
+ ///
+ /// Event that fires when a character is logging out.
+ ///
+ public event EventHandler Logout;
+
+ ///
+ /// Event that fires when a character is entering PvP.
+ ///
+ public event Action EnterPvP;
+
+ ///
+ /// Event that fires when a character is leaving PvP.
+ ///
+ public event Action LeavePvP;
+
+ ///
+ /// Event that gets fired when a duty is ready.
+ ///
+ public event EventHandler CfPop;
+
+ ///
+ /// Gets the language of the client.
+ ///
+ public ClientLanguage ClientLanguage { get; }
+
+ ///
+ /// Gets the current Territory the player resides in.
+ ///
+ public ushort TerritoryType { get; private set; }
+
+ ///
+ /// Gets the local player character, if one is present.
+ ///
+ public PlayerCharacter? LocalPlayer => Service.GetNullable()?[0] as PlayerCharacter;
+
+ ///
+ /// 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; }
+
+ ///
+ /// Gets a value indicating whether or not the user is playing PvP.
+ ///
+ public bool IsPvP { get; private set; }
+
+ ///
+ /// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den.
+ ///
+ public bool IsPvPExcludingDen { get; private set; }
+
+ ///
+ /// Gets client state address resolver.
+ ///
+ internal ClientStateAddressResolver AddressResolver => this.address;
+
+ ///
+ /// Dispose of managed and unmanaged resources.
+ ///
+ void IDisposable.Dispose()
+ {
+ this.setupTerritoryTypeHook.Dispose();
+ this.framework.Update -= this.FrameworkOnOnUpdateEvent;
+ this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
+ }
+
+ [ServiceManager.CallWhenServicesReady]
+ private void ContinueConstruction()
+ {
+ this.setupTerritoryTypeHook.Enable();
+ }
+
+ private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
+ {
+ this.TerritoryType = terriType;
+ this.TerritoryChanged?.InvokeSafely(this, terriType);
+
+ Log.Debug("TerritoryType changed: {0}", terriType);
+
+ return this.setupTerritoryTypeHook.Original(manager, terriType);
+ }
+
+ private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e)
+ {
+ this.CfPop?.InvokeSafely(this, e);
+ }
+
+ private void FrameworkOnOnUpdateEvent(Framework framework1)
+ {
+ var condition = Service.GetNullable();
+ var gameGui = Service.GetNullable();
+ var data = Service.GetNullable();
+
+ if (condition == null || gameGui == null || data == null)
+ return;
+
+ if (condition.Any() && this.lastConditionNone == true)
{
- this.address = new ClientStateAddressResolver();
- this.address.Setup(sigScanner);
-
- Log.Verbose("===== C L I E N T S T A T E =====");
-
- this.ClientLanguage = startInfo.Language;
-
- Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
-
- this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
-
- this.framework.Update += this.FrameworkOnOnUpdateEvent;
-
- this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
+ Log.Debug("Is login");
+ this.lastConditionNone = false;
+ this.IsLoggedIn = true;
+ this.Login?.InvokeSafely(this, null);
+ gameGui.ResetUiHideState();
}
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
-
- ///
- /// Event that gets fired when the current Territory changes.
- ///
- public event EventHandler TerritoryChanged;
-
- ///
- /// Event that fires when a character is logging in.
- ///
- public event EventHandler Login;
-
- ///
- /// Event that fires when a character is logging out.
- ///
- public event EventHandler Logout;
-
- ///
- /// Event that fires when a character is entering PvP.
- ///
- public event Action EnterPvP;
-
- ///
- /// Event that fires when a character is leaving PvP.
- ///
- public event Action LeavePvP;
-
- ///
- /// Event that gets fired when a duty is ready.
- ///
- public event EventHandler CfPop;
-
- ///
- /// Gets the language of the client.
- ///
- public ClientLanguage ClientLanguage { get; }
-
- ///
- /// Gets the current Territory the player resides in.
- ///
- public ushort TerritoryType { get; private set; }
-
- ///
- /// Gets the local player character, if one is present.
- ///
- public PlayerCharacter? LocalPlayer => Service.GetNullable()?[0] as PlayerCharacter;
-
- ///
- /// 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; }
-
- ///
- /// Gets a value indicating whether or not the user is playing PvP.
- ///
- public bool IsPvP { get; private set; }
-
- ///
- /// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den.
- ///
- public bool IsPvPExcludingDen { get; private set; }
-
- ///
- /// Gets client state address resolver.
- ///
- internal ClientStateAddressResolver AddressResolver => this.address;
-
- ///
- /// Dispose of managed and unmanaged resources.
- ///
- void IDisposable.Dispose()
+ if (!condition.Any() && this.lastConditionNone == false)
{
- this.setupTerritoryTypeHook.Dispose();
- this.framework.Update -= this.FrameworkOnOnUpdateEvent;
- this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
+ Log.Debug("Is logout");
+ this.lastConditionNone = true;
+ this.IsLoggedIn = false;
+ this.Logout?.InvokeSafely(this, null);
+ gameGui.ResetUiHideState();
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
+ this.IsPvP = GameMain.IsInPvPArea();
+ this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
+
+ if (this.IsPvP != this.lastFramePvP)
{
- this.setupTerritoryTypeHook.Enable();
- }
+ this.lastFramePvP = this.IsPvP;
- private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
- {
- this.TerritoryType = terriType;
- this.TerritoryChanged?.InvokeSafely(this, terriType);
-
- Log.Debug("TerritoryType changed: {0}", terriType);
-
- return this.setupTerritoryTypeHook.Original(manager, terriType);
- }
-
- private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e)
- {
- this.CfPop?.InvokeSafely(this, e);
- }
-
- private void FrameworkOnOnUpdateEvent(Framework framework1)
- {
- var condition = Service.GetNullable();
- var gameGui = Service.GetNullable();
- var data = Service.GetNullable();
-
- if (condition == null || gameGui == null || data == null)
- return;
-
- if (condition.Any() && this.lastConditionNone == true)
+ if (this.IsPvP)
{
- Log.Debug("Is login");
- this.lastConditionNone = false;
- this.IsLoggedIn = true;
- this.Login?.InvokeSafely(this, null);
- gameGui.ResetUiHideState();
+ this.EnterPvP?.InvokeSafely();
}
-
- if (!condition.Any() && this.lastConditionNone == false)
+ else
{
- Log.Debug("Is logout");
- this.lastConditionNone = true;
- this.IsLoggedIn = false;
- this.Logout?.InvokeSafely(this, null);
- gameGui.ResetUiHideState();
- }
-
- this.IsPvP = GameMain.IsInPvPArea();
- this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
-
- if (this.IsPvP != this.lastFramePvP)
- {
- this.lastFramePvP = this.IsPvP;
-
- if (this.IsPvP)
- {
- this.EnterPvP?.InvokeSafely();
- }
- else
- {
- this.LeavePvP?.InvokeSafely();
- }
+ this.LeavePvP?.InvokeSafely();
}
}
}
diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
index 4fa7feb79..98d3bc6dd 100644
--- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
+++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
@@ -1,128 +1,127 @@
using System;
-namespace Dalamud.Game.ClientState
+namespace Dalamud.Game.ClientState;
+
+///
+/// Client state memory address resolver.
+///
+public sealed class ClientStateAddressResolver : BaseAddressResolver
{
+ // Static offsets
+
///
- /// Client state memory address resolver.
+ /// Gets the address of the actor table.
///
- public sealed class ClientStateAddressResolver : BaseAddressResolver
+ public IntPtr ObjectTable { get; private set; }
+
+ ///
+ /// Gets the address of the buddy list.
+ ///
+ public IntPtr BuddyList { get; private set; }
+
+ ///
+ /// Gets the address of a pointer to the fate table.
+ ///
+ ///
+ /// This is a static address to a pointer, not the address of the table itself.
+ ///
+ public IntPtr FateTablePtr { get; private set; }
+
+ ///
+ /// Gets the address of the Group Manager.
+ ///
+ public IntPtr GroupManager { 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 keyboard state index array which translates the VK enumeration to the key state.
+ ///
+ public IntPtr KeyboardStateIndexArray { 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; }
+
+ ///
+ /// Gets the address of the Telepo instance.
+ ///
+ public IntPtr Telepo { get; private set; }
+
+ // Functions
+
+ ///
+ /// Gets the address of the method which sets the territory type.
+ ///
+ public IntPtr SetupTerritoryType { 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; }
+
+ ///
+ /// Gets the address of the method which updates the list of available teleport locations.
+ ///
+ public IntPtr UpdateAetheryteList { get; private set; }
+
+ ///
+ /// Scan for and setup any configured address pointers.
+ ///
+ /// The signature scanner to facilitate setup.
+ protected override void Setup64Bit(SigScanner sig)
{
- // Static offsets
+ // 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 ?? ?? ?? ??");
- ///
- /// Gets the address of the actor table.
- ///
- public IntPtr ObjectTable { get; private set; }
+ this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
- ///
- /// Gets the address of the buddy list.
- ///
- public IntPtr BuddyList { get; private set; }
+ this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04");
- ///
- /// Gets the address of a pointer to the fate table.
- ///
- ///
- /// This is a static address to a pointer, not the address of the table itself.
- ///
- public IntPtr FateTablePtr { get; private set; }
+ this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
- ///
- /// Gets the address of the Group Manager.
- ///
- public IntPtr GroupManager { get; private set; }
+ this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
- ///
- /// Gets the address of the local content id.
- ///
- public IntPtr LocalContentId { get; private set; }
+ this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
+ this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8;
- ///
- /// Gets the address of job gauge data.
- ///
- public IntPtr JobGaugeData { get; private set; }
+ this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
- ///
- /// Gets the address of the keyboard state.
- ///
- public IntPtr KeyboardState { get; private set; }
+ // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
+ // lea rcx, ds:1DB9F74h[rax*4] KeyboardState
+ // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray
+ this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
+ this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
- ///
- /// Gets the address of the keyboard state index array which translates the VK enumeration to the key state.
- ///
- public IntPtr KeyboardStateIndexArray { get; private set; }
+ this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
- ///
- /// Gets the address of the target manager.
- ///
- public IntPtr TargetManager { get; private set; }
+ this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
- ///
- /// Gets the address of the condition flag array.
- ///
- public IntPtr ConditionFlags { get; private set; }
+ this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
- ///
- /// Gets the address of the Telepo instance.
- ///
- public IntPtr Telepo { get; private set; }
+ this.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12");
- // Functions
-
- ///
- /// Gets the address of the method which sets the territory type.
- ///
- public IntPtr SetupTerritoryType { 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; }
-
- ///
- /// Gets the address of the method which updates the list of available teleport locations.
- ///
- public IntPtr UpdateAetheryteList { get; private set; }
-
- ///
- /// 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 ?? ?? ?? ??");
-
- this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
-
- this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04");
-
- this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
-
- this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
-
- this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
- this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8;
-
- this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
-
- // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
- // lea rcx, ds:1DB9F74h[rax*4] KeyboardState
- // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray
- this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
- this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
-
- this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
-
- this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
-
- this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
-
- this.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12");
-
- this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50");
- }
+ this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50");
}
}
diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs
index 6cf0bbdd5..8fcf59b00 100644
--- a/Dalamud/Game/ClientState/Conditions/Condition.cs
+++ b/Dalamud/Game/ClientState/Conditions/Condition.cs
@@ -4,152 +4,151 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Serilog;
-namespace Dalamud.Game.ClientState.Conditions
+namespace Dalamud.Game.ClientState.Conditions;
+
+///
+/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public sealed partial class Condition : IServiceType
{
///
- /// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
+ /// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public sealed partial class Condition : IServiceType
+ public const int MaxConditionEntries = 100;
+
+ private readonly bool[] cache = new bool[MaxConditionEntries];
+
+ [ServiceManager.ServiceConstructor]
+ private Condition(ClientState clientState)
{
- ///
- /// 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;
+ var resolver = clientState.AddressResolver;
+ this.Address = resolver.ConditionFlags;
+ }
- private readonly bool[] cache = new bool[MaxConditionEntries];
+ ///
+ /// A delegate type used with the event.
+ ///
+ /// The changed condition.
+ /// The value the condition is set to.
+ public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value);
- [ServiceManager.ServiceConstructor]
- private Condition(ClientState clientState)
+ ///
+ /// Event that gets fired when a condition is set.
+ /// Should only get fired for actual changes, so the previous value will always be !value.
+ ///
+ public event ConditionChangeDelegate? ConditionChange;
+
+ ///
+ /// Gets the condition array base pointer.
+ ///
+ public IntPtr Address { get; private set; }
+
+ ///
+ /// Check the value of a specific condition/state flag.
+ ///
+ /// The condition flag to check.
+ public unsafe bool this[int flag]
+ {
+ get
{
- var resolver = clientState.AddressResolver;
- this.Address = resolver.ConditionFlags;
+ if (flag < 0 || flag >= MaxConditionEntries)
+ return false;
+
+ return *(bool*)(this.Address + flag);
+ }
+ }
+
+ ///
+ public unsafe bool this[ConditionFlag flag]
+ => this[(int)flag];
+
+ ///
+ /// Check if any condition flags are set.
+ ///
+ /// Whether any single flag is set.
+ public bool Any()
+ {
+ for (var i = 0; i < MaxConditionEntries; i++)
+ {
+ var cond = this[i];
+
+ if (cond)
+ return true;
}
- ///
- /// A delegate type used with the event.
- ///
- /// The changed condition.
- /// The value the condition is set to.
- public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value);
+ return false;
+ }
- ///
- /// Event that gets fired when a condition is set.
- /// Should only get fired for actual changes, so the previous value will always be !value.
- ///
- public event ConditionChangeDelegate? ConditionChange;
+ [ServiceManager.CallWhenServicesReady]
+ private void ContinueConstruction(Framework framework)
+ {
+ // Initialization
+ for (var i = 0; i < MaxConditionEntries; i++)
+ this.cache[i] = this[i];
- ///
- /// Gets the condition array base pointer.
- ///
- public IntPtr Address { get; private set; }
+ framework.Update += this.FrameworkUpdate;
+ }
- ///
- /// Check the value of a specific condition/state flag.
- ///
- /// The condition flag to check.
- public unsafe bool this[int flag]
+ private void FrameworkUpdate(Framework framework)
+ {
+ for (var i = 0; i < MaxConditionEntries; i++)
{
- get
+ var value = this[i];
+
+ if (value != this.cache[i])
{
- if (flag < 0 || flag >= MaxConditionEntries)
- return false;
+ this.cache[i] = value;
- return *(bool*)(this.Address + flag);
- }
- }
-
- ///
- public unsafe bool this[ConditionFlag flag]
- => this[(int)flag];
-
- ///
- /// Check if any condition flags are set.
- ///
- /// Whether any single flag is set.
- public bool Any()
- {
- for (var i = 0; i < MaxConditionEntries; i++)
- {
- var cond = this[i];
-
- if (cond)
- return true;
- }
-
- return false;
- }
-
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(Framework framework)
- {
- // Initialization
- for (var i = 0; i < MaxConditionEntries; i++)
- this.cache[i] = this[i];
-
- framework.Update += this.FrameworkUpdate;
- }
-
- private void FrameworkUpdate(Framework framework)
- {
- for (var i = 0; i < MaxConditionEntries; i++)
- {
- var value = this[i];
-
- if (value != this.cache[i])
+ try
{
- this.cache[i] = value;
-
- try
- {
- this.ConditionChange?.Invoke((ConditionFlag)i, value);
- }
- catch (Exception ex)
- {
- Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown.");
- }
+ this.ConditionChange?.Invoke((ConditionFlag)i, value);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown.");
}
}
}
}
+}
+
+///
+/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
+///
+public sealed partial class Condition : IDisposable
+{
+ private bool isDisposed;
///
- /// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
+ /// Finalizes an instance of the class.
///
- public sealed partial class Condition : IDisposable
+ ~Condition()
{
- private bool isDisposed;
+ this.Dispose(false);
+ }
- ///
- /// Finalizes an instance of the class.
- ///
- ~Condition()
+ ///
+ /// Disposes this instance, alongside its hooks.
+ ///
+ void IDisposable.Dispose()
+ {
+ GC.SuppressFinalize(this);
+ this.Dispose(true);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ return;
+
+ if (disposing)
{
- this.Dispose(false);
+ Service.Get().Update -= this.FrameworkUpdate;
}
- ///
- /// Disposes this instance, alongside its hooks.
- ///
- void IDisposable.Dispose()
- {
- GC.SuppressFinalize(this);
- this.Dispose(true);
- }
-
- private void Dispose(bool disposing)
- {
- if (this.isDisposed)
- return;
-
- if (disposing)
- {
- Service.Get().Update -= this.FrameworkUpdate;
- }
-
- this.isDisposed = true;
- }
+ this.isDisposed = true;
}
}
diff --git a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
index 7d941304c..3c68d2e43 100644
--- a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
+++ b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
@@ -1,468 +1,467 @@
-namespace Dalamud.Game.ClientState.Conditions
+namespace Dalamud.Game.ClientState.Conditions;
+
+///
+/// Possible state flags (or conditions as they're called internally) that can be set on the local client.
+///
+/// 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
{
///
- /// Possible state flags (or conditions as they're called internally) that can be set on the local client.
- ///
- /// 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.
+ /// Unused.
///
- public enum ConditionFlag
- {
- ///
- /// Unused.
- ///
- None = 0,
-
- ///
- /// Unable to execute command under normal conditions.
- ///
- NormalConditions = 1,
-
- ///
- /// Unable to execute command while unconscious.
- ///
- Unconscious = 2,
-
- ///
- /// Unable to execute command during an emote.
- ///
- Emoting = 3,
-
- ///
- /// Unable to execute command while mounted.
- ///
- Mounted = 4,
-
- ///
- /// Unable to execute command while crafting.
- ///
- Crafting = 5,
-
- ///
- /// Unable to execute command while gathering.
- ///
- Gathering = 6,
-
- ///
- /// Unable to execute command while melding materia.
- ///
- MeldingMateria = 7,
-
- ///
- /// Unable to execute command while operating a siege machine.
- ///
- OperatingSiegeMachine = 8,
-
- ///
- /// Unable to execute command while carrying an object.
- ///
- CarryingObject = 9,
-
- ///
- /// Unable to execute command while mounted.
- ///
- Mounted2 = 10,
-
- ///
- /// Unable to execute command while in that position.
- ///
- InThatPosition = 11,
-
- ///
- /// Unable to execute command while chocobo racing.
- ///
- ChocoboRacing = 12,
-
- ///
- /// Unable to execute command while playing a mini-game.
- ///
- PlayingMiniGame = 13,
-
- ///
- /// Unable to execute command while playing Lord of Verminion.
- ///
- PlayingLordOfVerminion = 14,
-
- ///
- /// Unable to execute command while participating in a custom match.
- ///
- ParticipatingInCustomMatch = 15,
-
- ///
- /// Unable to execute command while performing.
- ///
- Performing = 16,
-
- // Unknown17 = 17,
- // Unknown18 = 18,
- // Unknown19 = 19,
- // Unknown20 = 20,
- // Unknown21 = 21,
- // Unknown22 = 22,
- // Unknown23 = 23,
- // Unknown24 = 24,
-
- ///
- /// Unable to execute command while occupied.
- ///
- Occupied = 25,
-
- ///
- /// Unable to execute command during combat.
- ///
- InCombat = 26,
-
- ///
- /// Unable to execute command while casting.
- ///
- Casting = 27,
-
- ///
- /// Unable to execute command while suffering status affliction.
- ///
- SufferingStatusAffliction = 28,
-
- ///
- /// Unable to execute command while suffering status affliction.
- ///
- SufferingStatusAffliction2 = 29,
-
- ///
- /// Unable to execute command while occupied.
- ///
- Occupied30 = 30,
-
- ///
- /// Unable to execute command while occupied.
- ///
- // todo: not sure if this is used for other event states/???
- OccupiedInEvent = 31,
-
- ///
- /// Unable to execute command while occupied.
- ///
- OccupiedInQuestEvent = 32,
-
- ///
- /// Unable to execute command while occupied.
- ///
- Occupied33 = 33,
-
- ///
- /// Unable to execute command while bound by duty.
- ///
- BoundByDuty = 34,
-
- ///
- /// Unable to execute command while occupied.
- ///
- OccupiedInCutSceneEvent = 35,
-
- ///
- /// Unable to execute command while in a dueling area.
- ///
- InDuelingArea = 36,
-
- ///
- /// Unable to execute command while a trade is open.
- ///
- TradeOpen = 37,
-
- ///
- /// Unable to execute command while occupied.
- ///
- Occupied38 = 38,
-
- ///
- /// Unable to execute command while occupied.
- ///
- Occupied39 = 39,
-
- ///
- /// Unable to execute command while crafting.
- ///
- Crafting40 = 40,
-
- ///
- /// Unable to execute command while preparing to craft.
- ///
- PreparingToCraft = 41,
-
- ///
- /// Unable to execute command while gathering.
- ///
- Gathering42 = 42,
-
- ///
- /// Unable to execute command while fishing.
- ///
- Fishing = 43,
-
- // Unknown44 = 44,
-
- ///
- /// Unable to execute command while between areas.
- ///
- BetweenAreas = 45,
-
- ///
- /// Unable to execute command while stealthed.
- ///
- Stealthed = 46,
-
- // Unknown47 = 47,
-
- ///
- /// Unable to execute command while jumping.
- ///
- Jumping = 48,
-
- ///
- /// Unable to execute command while auto-run is active.
- ///
- AutorunActive = 49,
-
- ///
- /// Unable to execute command while occupied.
- ///
- // todo: used for other shits?
- OccupiedSummoningBell = 50,
-
- ///
- /// Unable to execute command while between areas.
- ///
- BetweenAreas51 = 51,
-
- ///
- /// Unable to execute command due to system error.
- ///
- SystemError = 52,
-
- ///
- /// Unable to execute command while logging out.
- ///
- LoggingOut = 53,
-
- ///
- /// Unable to execute command at this location.
- ///
- ConditionLocation = 54,
-
- ///
- /// Unable to execute command while waiting for duty.
- ///
- WaitingForDuty = 55,
-
- ///
- /// Unable to execute command while bound by duty.
- ///
- BoundByDuty56 = 56,
-
- ///
- /// Unable to execute command at this time.
- ///
- Unknown57 = 57,
-
- ///
- /// Unable to execute command while watching a cutscene.
- ///
- WatchingCutscene = 58,
-
- ///
- /// Unable to execute command while waiting for Duty Finder.
- ///
- WaitingForDutyFinder = 59,
-
- ///
- /// Unable to execute command while creating a character.
- ///
- CreatingCharacter = 60,
-
- ///
- /// Unable to execute command while jumping.
- ///
- Jumping61 = 61,
-
- ///
- /// Unable to execute command while the PvP display is active.
- ///
- PvPDisplayActive = 62,
-
- ///
- /// Unable to execute command while suffering status affliction.
- ///
- SufferingStatusAffliction63 = 63,
-
- ///
- /// Unable to execute command while mounting.
- ///
- Mounting = 64,
-
- ///
- /// Unable to execute command while carrying an item.
- ///
- CarryingItem = 65,
-
- ///
- /// Unable to execute command while using the Party Finder.
- ///
- UsingPartyFinder = 66,
-
- ///
- /// Unable to execute command while using housing functions.
- ///
- UsingHousingFunctions = 67,
-
- ///
- /// Unable to execute command while transformed.
- ///
- Transformed = 68,
-
- ///
- /// Unable to execute command while on the free trial.
- ///
- OnFreeTrial = 69,
-
- ///
- /// Unable to execute command while being moved.
- ///
- BeingMoved = 70,
-
- ///
- /// Unable to execute command while mounting.
- ///
- Mounting71 = 71,
-
- ///
- /// Unable to execute command while suffering status affliction.
- ///
- SufferingStatusAffliction72 = 72,
-
- ///
- /// Unable to execute command while suffering status affliction.
- ///
- SufferingStatusAffliction73 = 73,
-
- ///
- /// Unable to execute command while registering for a race or match.
- ///
- RegisteringForRaceOrMatch = 74,
-
- ///
- /// Unable to execute command while waiting for a race or match.
- ///
- WaitingForRaceOrMatch = 75,
-
- ///
- /// Unable to execute command while waiting for a Triple Triad match.
- ///
- WaitingForTripleTriadMatch = 76,
-
- ///
- /// Unable to execute command while in flight.
- ///
- InFlight = 77,
-
- ///
- /// Unable to execute command while watching a cutscene.
- ///
- WatchingCutscene78 = 78,
-
- ///
- /// Unable to execute command while delving into a deep dungeon.
- ///
- InDeepDungeon = 79,
-
- ///
- /// Unable to execute command while swimming.
- ///
- Swimming = 80,
-
- ///
- /// Unable to execute command while diving.
- ///
- Diving = 81,
-
- ///
- /// Unable to execute command while registering for a Triple Triad match.
- ///
- RegisteringForTripleTriadMatch = 82,
-
- ///
- /// Unable to execute command while waiting for a Triple Triad match.
- ///
- WaitingForTripleTriadMatch83 = 83,
-
- ///
- /// Unable to execute command while participating in a cross-world party or alliance.
- ///
- ParticipatingInCrossWorldPartyOrAlliance = 84,
-
- // Unknown85 = 85,
-
- ///
- /// Unable to execute command while playing duty record.
- ///
- DutyRecorderPlayback = 86,
-
- ///
- /// Unable to execute command while casting.
- ///
- Casting87 = 87,
-
- ///
- /// Unable to execute command in this state.
- ///
- InThisState88 = 88,
-
- ///
- /// Unable to execute command in this state.
- ///
- InThisState89 = 89,
-
- ///
- /// Unable to execute command while role-playing.
- ///
- RolePlaying = 90,
-
- ///
- /// Unable to execute command while bound by duty.
- ///
- BoundToDuty97 = 91,
-
- ///
- /// Unable to execute command while readying to visit another World.
- ///
- ReadyingVisitOtherWorld = 92,
-
- ///
- /// Unable to execute command while waiting to visit another World.
- ///
- WaitingToVisitOtherWorld = 93,
-
- ///
- /// Unable to execute command while using a parasol.
- ///
- UsingParasol = 94,
-
- ///
- /// Unable to execute command while bound by duty.
- ///
- BoundByDuty95 = 95,
-
- ///
- /// Cannot execute at this time.
- ///
- Unknown96 = 96,
-
- ///
- /// Unable to execute command while wearing a guise.
- ///
- Disguised = 97,
-
- ///
- /// Unable to execute command while recruiting for a non-cross-world party.
- ///
- RecruitingWorldOnly = 98,
- }
+ None = 0,
+
+ ///
+ /// Unable to execute command under normal conditions.
+ ///
+ NormalConditions = 1,
+
+ ///
+ /// Unable to execute command while unconscious.
+ ///
+ Unconscious = 2,
+
+ ///
+ /// Unable to execute command during an emote.
+ ///
+ Emoting = 3,
+
+ ///
+ /// Unable to execute command while mounted.
+ ///
+ Mounted = 4,
+
+ ///
+ /// Unable to execute command while crafting.
+ ///
+ Crafting = 5,
+
+ ///
+ /// Unable to execute command while gathering.
+ ///
+ Gathering = 6,
+
+ ///
+ /// Unable to execute command while melding materia.
+ ///
+ MeldingMateria = 7,
+
+ ///
+ /// Unable to execute command while operating a siege machine.
+ ///
+ OperatingSiegeMachine = 8,
+
+ ///
+ /// Unable to execute command while carrying an object.
+ ///
+ CarryingObject = 9,
+
+ ///
+ /// Unable to execute command while mounted.
+ ///
+ Mounted2 = 10,
+
+ ///
+ /// Unable to execute command while in that position.
+ ///
+ InThatPosition = 11,
+
+ ///
+ /// Unable to execute command while chocobo racing.
+ ///
+ ChocoboRacing = 12,
+
+ ///
+ /// Unable to execute command while playing a mini-game.
+ ///
+ PlayingMiniGame = 13,
+
+ ///
+ /// Unable to execute command while playing Lord of Verminion.
+ ///
+ PlayingLordOfVerminion = 14,
+
+ ///
+ /// Unable to execute command while participating in a custom match.
+ ///
+ ParticipatingInCustomMatch = 15,
+
+ ///
+ /// Unable to execute command while performing.
+ ///
+ Performing = 16,
+
+ // Unknown17 = 17,
+ // Unknown18 = 18,
+ // Unknown19 = 19,
+ // Unknown20 = 20,
+ // Unknown21 = 21,
+ // Unknown22 = 22,
+ // Unknown23 = 23,
+ // Unknown24 = 24,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ Occupied = 25,
+
+ ///
+ /// Unable to execute command during combat.
+ ///
+ InCombat = 26,
+
+ ///
+ /// Unable to execute command while casting.
+ ///
+ Casting = 27,
+
+ ///
+ /// Unable to execute command while suffering status affliction.
+ ///
+ SufferingStatusAffliction = 28,
+
+ ///
+ /// Unable to execute command while suffering status affliction.
+ ///
+ SufferingStatusAffliction2 = 29,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ Occupied30 = 30,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ // todo: not sure if this is used for other event states/???
+ OccupiedInEvent = 31,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ OccupiedInQuestEvent = 32,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ Occupied33 = 33,
+
+ ///
+ /// Unable to execute command while bound by duty.
+ ///
+ BoundByDuty = 34,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ OccupiedInCutSceneEvent = 35,
+
+ ///
+ /// Unable to execute command while in a dueling area.
+ ///
+ InDuelingArea = 36,
+
+ ///
+ /// Unable to execute command while a trade is open.
+ ///
+ TradeOpen = 37,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ Occupied38 = 38,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ Occupied39 = 39,
+
+ ///
+ /// Unable to execute command while crafting.
+ ///
+ Crafting40 = 40,
+
+ ///
+ /// Unable to execute command while preparing to craft.
+ ///
+ PreparingToCraft = 41,
+
+ ///
+ /// Unable to execute command while gathering.
+ ///
+ Gathering42 = 42,
+
+ ///
+ /// Unable to execute command while fishing.
+ ///
+ Fishing = 43,
+
+ // Unknown44 = 44,
+
+ ///
+ /// Unable to execute command while between areas.
+ ///
+ BetweenAreas = 45,
+
+ ///
+ /// Unable to execute command while stealthed.
+ ///
+ Stealthed = 46,
+
+ // Unknown47 = 47,
+
+ ///
+ /// Unable to execute command while jumping.
+ ///
+ Jumping = 48,
+
+ ///
+ /// Unable to execute command while auto-run is active.
+ ///
+ AutorunActive = 49,
+
+ ///
+ /// Unable to execute command while occupied.
+ ///
+ // todo: used for other shits?
+ OccupiedSummoningBell = 50,
+
+ ///
+ /// Unable to execute command while between areas.
+ ///
+ BetweenAreas51 = 51,
+
+ ///
+ /// Unable to execute command due to system error.
+ ///
+ SystemError = 52,
+
+ ///
+ /// Unable to execute command while logging out.
+ ///
+ LoggingOut = 53,
+
+ ///
+ /// Unable to execute command at this location.
+ ///
+ ConditionLocation = 54,
+
+ ///
+ /// Unable to execute command while waiting for duty.
+ ///
+ WaitingForDuty = 55,
+
+ ///
+ /// Unable to execute command while bound by duty.
+ ///
+ BoundByDuty56 = 56,
+
+ ///
+ /// Unable to execute command at this time.
+ ///
+ Unknown57 = 57,
+
+ ///
+ /// Unable to execute command while watching a cutscene.
+ ///
+ WatchingCutscene = 58,
+
+ ///
+ /// Unable to execute command while waiting for Duty Finder.
+ ///
+ WaitingForDutyFinder = 59,
+
+ ///
+ /// Unable to execute command while creating a character.
+ ///
+ CreatingCharacter = 60,
+
+ ///
+ /// Unable to execute command while jumping.
+ ///
+ Jumping61 = 61,
+
+ ///
+ /// Unable to execute command while the PvP display is active.
+ ///
+ PvPDisplayActive = 62,
+
+ ///
+ /// Unable to execute command while suffering status affliction.
+ ///
+ SufferingStatusAffliction63 = 63,
+
+ ///
+ /// Unable to execute command while mounting.
+ ///
+ Mounting = 64,
+
+ ///
+ /// Unable to execute command while carrying an item.
+ ///
+ CarryingItem = 65,
+
+ ///
+ /// Unable to execute command while using the Party Finder.
+ ///
+ UsingPartyFinder = 66,
+
+ ///
+ /// Unable to execute command while using housing functions.
+ ///
+ UsingHousingFunctions = 67,
+
+ ///
+ /// Unable to execute command while transformed.
+ ///
+ Transformed = 68,
+
+ ///
+ /// Unable to execute command while on the free trial.
+ ///
+ OnFreeTrial = 69,
+
+ ///
+ /// Unable to execute command while being moved.
+ ///
+ BeingMoved = 70,
+
+ ///
+ /// Unable to execute command while mounting.
+ ///
+ Mounting71 = 71,
+
+ ///
+ /// Unable to execute command while suffering status affliction.
+ ///
+ SufferingStatusAffliction72 = 72,
+
+ ///
+ /// Unable to execute command while suffering status affliction.
+ ///
+ SufferingStatusAffliction73 = 73,
+
+ ///
+ /// Unable to execute command while registering for a race or match.
+ ///
+ RegisteringForRaceOrMatch = 74,
+
+ ///
+ /// Unable to execute command while waiting for a race or match.
+ ///
+ WaitingForRaceOrMatch = 75,
+
+ ///
+ /// Unable to execute command while waiting for a Triple Triad match.
+ ///
+ WaitingForTripleTriadMatch = 76,
+
+ ///
+ /// Unable to execute command while in flight.
+ ///
+ InFlight = 77,
+
+ ///
+ /// Unable to execute command while watching a cutscene.
+ ///
+ WatchingCutscene78 = 78,
+
+ ///
+ /// Unable to execute command while delving into a deep dungeon.
+ ///
+ InDeepDungeon = 79,
+
+ ///
+ /// Unable to execute command while swimming.
+ ///
+ Swimming = 80,
+
+ ///
+ /// Unable to execute command while diving.
+ ///
+ Diving = 81,
+
+ ///
+ /// Unable to execute command while registering for a Triple Triad match.
+ ///
+ RegisteringForTripleTriadMatch = 82,
+
+ ///
+ /// Unable to execute command while waiting for a Triple Triad match.
+ ///
+ WaitingForTripleTriadMatch83 = 83,
+
+ ///
+ /// Unable to execute command while participating in a cross-world party or alliance.
+ ///
+ ParticipatingInCrossWorldPartyOrAlliance = 84,
+
+ // Unknown85 = 85,
+
+ ///
+ /// Unable to execute command while playing duty record.
+ ///
+ DutyRecorderPlayback = 86,
+
+ ///
+ /// Unable to execute command while casting.
+ ///
+ Casting87 = 87,
+
+ ///
+ /// Unable to execute command in this state.
+ ///
+ InThisState88 = 88,
+
+ ///
+ /// Unable to execute command in this state.
+ ///
+ InThisState89 = 89,
+
+ ///
+ /// Unable to execute command while role-playing.
+ ///
+ RolePlaying = 90,
+
+ ///
+ /// Unable to execute command while bound by duty.
+ ///
+ BoundToDuty97 = 91,
+
+ ///
+ /// Unable to execute command while readying to visit another World.
+ ///
+ ReadyingVisitOtherWorld = 92,
+
+ ///
+ /// Unable to execute command while waiting to visit another World.
+ ///
+ WaitingToVisitOtherWorld = 93,
+
+ ///
+ /// Unable to execute command while using a parasol.
+ ///
+ UsingParasol = 94,
+
+ ///
+ /// Unable to execute command while bound by duty.
+ ///
+ BoundByDuty95 = 95,
+
+ ///
+ /// Cannot execute at this time.
+ ///
+ Unknown96 = 96,
+
+ ///
+ /// Unable to execute command while wearing a guise.
+ ///
+ Disguised = 97,
+
+ ///
+ /// Unable to execute command while recruiting for a non-cross-world party.
+ ///
+ RecruitingWorldOnly = 98,
}
diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs
index 1e5176a9a..440767846 100644
--- a/Dalamud/Game/ClientState/Fates/Fate.cs
+++ b/Dalamud/Game/ClientState/Fates/Fate.cs
@@ -6,131 +6,130 @@ using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
-namespace Dalamud.Game.ClientState.Fates
+namespace Dalamud.Game.ClientState.Fates;
+
+///
+/// This class represents an FFXIV Fate.
+///
+public unsafe partial class Fate : IEquatable
{
///
- /// This class represents an FFXIV Fate.
+ /// Initializes a new instance of the class.
///
- public unsafe partial class Fate : IEquatable
+ /// The address of this fate in memory.
+ internal Fate(IntPtr address)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The address of this fate in memory.
- internal Fate(IntPtr address)
- {
- this.Address = address;
- }
-
- ///
- /// Gets the address of this Fate in memory.
- ///
- public IntPtr Address { get; }
-
- private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
-
- public static bool operator ==(Fate fate1, Fate fate2)
- {
- if (fate1 is null || fate2 is null)
- return Equals(fate1, fate2);
-
- return fate1.Equals(fate2);
- }
-
- public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
-
- ///
- /// Gets a value indicating whether this Fate is still valid in memory.
- ///
- /// The fate to check.
- /// True or false.
- public static bool IsValid(Fate fate)
- {
- var clientState = Service.GetNullable();
-
- if (fate == null || clientState == null)
- return false;
-
- if (clientState.LocalContentId == 0)
- return false;
-
- return true;
- }
-
- ///
- /// Gets a value indicating whether this actor is still valid in memory.
- ///
- /// True or false.
- public bool IsValid() => IsValid(this);
-
- ///
- bool IEquatable.Equals(Fate other) => this.FateId == other?.FateId;
-
- ///
- public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as Fate);
-
- ///
- public override int GetHashCode() => this.FateId.GetHashCode();
+ this.Address = address;
}
///
- /// This class represents an FFXIV Fate.
+ /// Gets the address of this Fate in memory.
///
- public unsafe partial class Fate
+ public IntPtr Address { get; }
+
+ private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
+
+ public static bool operator ==(Fate fate1, Fate fate2)
{
- ///
- /// Gets the Fate ID of this .
- ///
- public ushort FateId => this.Struct->FateId;
+ if (fate1 is null || fate2 is null)
+ return Equals(fate1, fate2);
- ///
- /// Gets game data linked to this Fate.
- ///
- public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId);
-
- ///
- /// Gets the time this started.
- ///
- public int StartTimeEpoch => this.Struct->StartTimeEpoch;
-
- ///
- /// Gets how long this will run.
- ///
- public short Duration => this.Struct->Duration;
-
- ///
- /// Gets the remaining time in seconds for this .
- ///
- public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
-
- ///
- /// Gets the displayname of this .
- ///
- public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
-
- ///
- /// Gets the state of this (Running, Ended, Failed, Preparation, WaitingForEnd).
- ///
- public FateState State => (FateState)this.Struct->State;
-
- ///
- /// Gets the progress amount of this .
- ///
- public byte Progress => this.Struct->Progress;
-
- ///
- /// Gets the level of this .
- ///
- public byte Level => this.Struct->Level;
-
- ///
- /// Gets the position of this .
- ///
- public Vector3 Position => this.Struct->Location;
-
- ///
- /// Gets the territory this is located in.
- ///
- public ExcelResolver TerritoryType => new(this.Struct->TerritoryId);
+ return fate1.Equals(fate2);
}
+
+ public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
+
+ ///
+ /// Gets a value indicating whether this Fate is still valid in memory.
+ ///
+ /// The fate to check.
+ /// True or false.
+ public static bool IsValid(Fate fate)
+ {
+ var clientState = Service.GetNullable();
+
+ if (fate == null || clientState == null)
+ return false;
+
+ if (clientState.LocalContentId == 0)
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Gets a value indicating whether this actor is still valid in memory.
+ ///
+ /// True or false.
+ public bool IsValid() => IsValid(this);
+
+ ///
+ bool IEquatable.Equals(Fate other) => this.FateId == other?.FateId;
+
+ ///
+ public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as Fate);
+
+ ///
+ public override int GetHashCode() => this.FateId.GetHashCode();
+}
+
+///
+/// This class represents an FFXIV Fate.
+///
+public unsafe partial class Fate
+{
+ ///
+ /// Gets the Fate ID of this .
+ ///
+ public ushort FateId => this.Struct->FateId;
+
+ ///
+ /// Gets game data linked to this Fate.
+ ///
+ public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId);
+
+ ///
+ /// Gets the time this started.
+ ///
+ public int StartTimeEpoch => this.Struct->StartTimeEpoch;
+
+ ///
+ /// Gets how long this will run.
+ ///
+ public short Duration => this.Struct->Duration;
+
+ ///
+ /// Gets the remaining time in seconds for this .
+ ///
+ public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
+
+ ///
+ /// Gets the displayname of this .
+ ///
+ public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
+
+ ///
+ /// Gets the state of this (Running, Ended, Failed, Preparation, WaitingForEnd).
+ ///
+ public FateState State => (FateState)this.Struct->State;
+
+ ///
+ /// Gets the progress amount of this .
+ ///
+ public byte Progress => this.Struct->Progress;
+
+ ///
+ /// Gets the level of this .
+ ///
+ public byte Level => this.Struct->Level;
+
+ ///
+ /// Gets the position of this .
+ ///
+ public Vector3 Position => this.Struct->Location;
+
+ ///
+ /// Gets the territory this is located in.
+ ///
+ public ExcelResolver TerritoryType => new(this.Struct->TerritoryId);
}
diff --git a/Dalamud/Game/ClientState/Fates/FateState.cs b/Dalamud/Game/ClientState/Fates/FateState.cs
index c7a789231..8f2ef85cc 100644
--- a/Dalamud/Game/ClientState/Fates/FateState.cs
+++ b/Dalamud/Game/ClientState/Fates/FateState.cs
@@ -1,33 +1,32 @@
-namespace Dalamud.Game.ClientState.Fates
+namespace Dalamud.Game.ClientState.Fates;
+
+///
+/// This represents the state of a single Fate.
+///
+public enum FateState : byte
{
///
- /// This represents the state of a single Fate.
+ /// The Fate is active.
///
- public enum FateState : byte
- {
- ///
- /// The Fate is active.
- ///
- Running = 0x02,
+ Running = 0x02,
- ///
- /// The Fate has ended.
- ///
- Ended = 0x04,
+ ///
+ /// The Fate has ended.
+ ///
+ Ended = 0x04,
- ///
- /// The player failed the Fate.
- ///
- Failed = 0x05,
+ ///
+ /// The player failed the Fate.
+ ///
+ Failed = 0x05,
- ///
- /// The Fate is preparing to run.
- ///
- Preparation = 0x07,
+ ///
+ /// The Fate is preparing to run.
+ ///
+ Preparation = 0x07,
- ///
- /// The Fate is preparing to end.
- ///
- WaitingForEnd = 0x08,
- }
+ ///
+ /// The Fate is preparing to end.
+ ///
+ WaitingForEnd = 0x08,
}
diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs
index 93a0e60b9..dfd4bcaee 100644
--- a/Dalamud/Game/ClientState/Fates/FateTable.cs
+++ b/Dalamud/Game/ClientState/Fates/FateTable.cs
@@ -6,137 +6,136 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Serilog;
-namespace Dalamud.Game.ClientState.Fates
+namespace Dalamud.Game.ClientState.Fates;
+
+///
+/// This collection represents the currently available Fate events.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public sealed partial class FateTable : IServiceType
{
- ///
- /// This collection represents the currently available Fate events.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public sealed partial class FateTable : IServiceType
+ private readonly ClientStateAddressResolver address;
+
+ [ServiceManager.ServiceConstructor]
+ private FateTable(ClientState clientState)
{
- private readonly ClientStateAddressResolver address;
+ this.address = clientState.AddressResolver;
- [ServiceManager.ServiceConstructor]
- private FateTable(ClientState clientState)
+ Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
+ }
+
+ ///
+ /// Gets the address of the Fate table.
+ ///
+ public IntPtr Address => this.address.FateTablePtr;
+
+ ///
+ /// Gets the amount of currently active Fates.
+ ///
+ public unsafe int Length
+ {
+ get
{
- this.address = clientState.AddressResolver;
-
- Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
- }
-
- ///
- /// Gets the address of the Fate table.
- ///
- public IntPtr Address => this.address.FateTablePtr;
-
- ///
- /// Gets the amount of currently active Fates.
- ///
- public unsafe int Length
- {
- get
- {
- var fateTable = this.FateTableAddress;
- if (fateTable == IntPtr.Zero)
- return 0;
-
- // Sonar used this to check if the table was safe to read
- if (Struct->FateDirector == null)
- return 0;
-
- if (Struct->Fates.First == null || Struct->Fates.Last == null)
- return 0;
-
- return (int)Struct->Fates.Size();
- }
- }
-
- ///
- /// Gets the address of the Fate table.
- ///
- internal unsafe IntPtr FateTableAddress
- {
- get
- {
- if (this.address.FateTablePtr == IntPtr.Zero)
- return IntPtr.Zero;
-
- return *(IntPtr*)this.address.FateTablePtr;
- }
- }
-
- private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
-
- ///
- /// Get an actor at the specified spawn index.
- ///
- /// Spawn index.
- /// A at the specified spawn index.
- public Fate? this[int index]
- {
- get
- {
- var address = this.GetFateAddress(index);
- return this.CreateFateReference(address);
- }
- }
-
- ///
- /// Gets the address of the Fate at the specified index of the fate table.
- ///
- /// The index of the Fate.
- /// The memory address of the Fate.
- public unsafe IntPtr GetFateAddress(int index)
- {
- if (index >= this.Length)
- return IntPtr.Zero;
-
var fateTable = this.FateTableAddress;
if (fateTable == IntPtr.Zero)
- return IntPtr.Zero;
+ return 0;
- return (IntPtr)this.Struct->Fates.Get((ulong)index).Value;
- }
+ // Sonar used this to check if the table was safe to read
+ if (Struct->FateDirector == null)
+ return 0;
- ///
- /// Create a reference to a FFXIV actor.
- ///
- /// The offset of the actor in memory.
- /// object containing requested data.
- public Fate? CreateFateReference(IntPtr offset)
- {
- var clientState = Service.Get();
+ if (Struct->Fates.First == null || Struct->Fates.Last == null)
+ return 0;
- if (clientState.LocalContentId == 0)
- return null;
-
- if (offset == IntPtr.Zero)
- return null;
-
- return new Fate(offset);
+ return (int)Struct->Fates.Size();
}
}
///
- /// This collection represents the currently available Fate events.
+ /// Gets the address of the Fate table.
///
- public sealed partial class FateTable : IReadOnlyCollection
+ internal unsafe IntPtr FateTableAddress
{
- ///
- int IReadOnlyCollection.Count => this.Length;
-
- ///
- public IEnumerator GetEnumerator()
+ get
{
- for (var i = 0; i < this.Length; i++)
- {
- yield return this[i];
- }
- }
+ if (this.address.FateTablePtr == IntPtr.Zero)
+ return IntPtr.Zero;
- ///
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ return *(IntPtr*)this.address.FateTablePtr;
+ }
+ }
+
+ private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
+
+ ///
+ /// Get an actor at the specified spawn index.
+ ///
+ /// Spawn index.
+ /// A at the specified spawn index.
+ public Fate? this[int index]
+ {
+ get
+ {
+ var address = this.GetFateAddress(index);
+ return this.CreateFateReference(address);
+ }
+ }
+
+ ///
+ /// Gets the address of the Fate at the specified index of the fate table.
+ ///
+ /// The index of the Fate.
+ /// The memory address of the Fate.
+ public unsafe IntPtr GetFateAddress(int index)
+ {
+ if (index >= this.Length)
+ return IntPtr.Zero;
+
+ var fateTable = this.FateTableAddress;
+ if (fateTable == IntPtr.Zero)
+ return IntPtr.Zero;
+
+ return (IntPtr)this.Struct->Fates.Get((ulong)index).Value;
+ }
+
+ ///
+ /// Create a reference to a FFXIV actor.
+ ///
+ /// The offset of the actor in memory.
+ /// object containing requested data.
+ public Fate? CreateFateReference(IntPtr offset)
+ {
+ var clientState = Service.Get();
+
+ if (clientState.LocalContentId == 0)
+ return null;
+
+ if (offset == IntPtr.Zero)
+ return null;
+
+ return new Fate(offset);
}
}
+
+///
+/// This collection represents the currently available Fate events.
+///
+public sealed partial class FateTable : IReadOnlyCollection
+{
+ ///
+ int IReadOnlyCollection.Count => this.Length;
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ for (var i = 0; i < this.Length; i++)
+ {
+ yield return this[i];
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+}
diff --git a/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs b/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs
index 7813803c8..a73f72857 100644
--- a/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs
+++ b/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs
@@ -1,96 +1,95 @@
using System;
-namespace Dalamud.Game.ClientState.GamePad
+namespace Dalamud.Game.ClientState.GamePad;
+
+///
+/// Bitmask of the Button ushort used by the game.
+///
+[Flags]
+public enum GamepadButtons : ushort
{
///
- /// Bitmask of the Button ushort used by the game.
+ /// No buttons pressed.
///
- [Flags]
- public enum GamepadButtons : ushort
- {
- ///
- /// No buttons pressed.
- ///
- None = 0,
+ None = 0,
- ///
- /// Digipad up.
- ///
- DpadUp = 0x0001,
+ ///
+ /// Digipad up.
+ ///
+ DpadUp = 0x0001,
- ///
- /// Digipad down.
- ///
- DpadDown = 0x0002,
+ ///
+ /// Digipad down.
+ ///
+ DpadDown = 0x0002,
- ///
- /// Digipad left.
- ///
- DpadLeft = 0x0004,
+ ///
+ /// Digipad left.
+ ///
+ DpadLeft = 0x0004,
- ///
- /// Digipad right.
- ///
- DpadRight = 0x0008,
+ ///
+ /// Digipad right.
+ ///
+ DpadRight = 0x0008,
- ///
- /// North action button. Triangle on PS, Y on Xbox.
- ///
- North = 0x0010,
+ ///
+ /// North action button. Triangle on PS, Y on Xbox.
+ ///
+ North = 0x0010,
- ///
- /// South action button. Cross on PS, A on Xbox.
- ///
- South = 0x0020,
+ ///
+ /// South action button. Cross on PS, A on Xbox.
+ ///
+ South = 0x0020,
- ///
- /// West action button. Square on PS, X on Xbos.
- ///
- West = 0x0040,
+ ///
+ /// West action button. Square on PS, X on Xbos.
+ ///
+ West = 0x0040,
- ///
- /// East action button. Circle on PS, B on Xbox.
- ///
- East = 0x0080,
+ ///
+ /// East action button. Circle on PS, B on Xbox.
+ ///
+ East = 0x0080,
- ///
- /// First button on left shoulder side.
- ///
- L1 = 0x0100,
+ ///
+ /// First button on left shoulder side.
+ ///
+ L1 = 0x0100,
- ///
- /// Second button on left shoulder side. Analog input lost in this bitmask.
- ///
- L2 = 0x0200,
+ ///
+ /// Second button on left shoulder side. Analog input lost in this bitmask.
+ ///
+ L2 = 0x0200,
- ///
- /// Press on left analogue stick.
- ///
- L3 = 0x0400,
+ ///
+ /// Press on left analogue stick.
+ ///
+ L3 = 0x0400,
- ///
- /// First button on right shoulder.
- ///
- R1 = 0x0800,
+ ///
+ /// First button on right shoulder.
+ ///
+ R1 = 0x0800,
- ///
- /// Second button on right shoulder. Analog input lost in this bitmask.
- ///
- R2 = 0x1000,
+ ///
+ /// Second button on right shoulder. Analog input lost in this bitmask.
+ ///
+ R2 = 0x1000,
- ///
- /// Press on right analogue stick.
- ///
- R3 = 0x2000,
+ ///
+ /// Press on right analogue stick.
+ ///
+ R3 = 0x2000,
- ///
- /// Button on the right inner side of the controller. Options on PS, Start on Xbox.
- ///
- Start = 0x8000,
+ ///
+ /// Button on the right inner side of the controller. Options on PS, Start on Xbox.
+ ///
+ Start = 0x8000,
- ///
- /// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
- ///
- Select = 0x4000,
- }
+ ///
+ /// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
+ ///
+ Select = 0x4000,
}
diff --git a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs
index d6d46a0cc..32439cd08 100644
--- a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs
+++ b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs
@@ -1,76 +1,75 @@
using System.Runtime.InteropServices;
-namespace Dalamud.Game.ClientState.GamePad
+namespace Dalamud.Game.ClientState.GamePad;
+
+///
+/// Struct which gets populated by polling the gamepads.
+///
+/// Has an array of gamepads, among many other things (here not mapped).
+/// All we really care about is the final data which the game uses to determine input.
+///
+/// The size is definitely bigger than only the following fields but I do not know how big.
+///
+[StructLayout(LayoutKind.Explicit)]
+public struct GamepadInput
{
///
- /// Struct which gets populated by polling the gamepads.
- ///
- /// Has an array of gamepads, among many other things (here not mapped).
- /// All we really care about is the final data which the game uses to determine input.
- ///
- /// The size is definitely bigger than only the following fields but I do not know how big.
+ /// Left analogue stick's horizontal value, -99 for left, 99 for right.
///
- [StructLayout(LayoutKind.Explicit)]
- public struct GamepadInput
- {
- ///
- /// Left analogue stick's horizontal value, -99 for left, 99 for right.
- ///
- [FieldOffset(0x88)]
- public int LeftStickX;
+ [FieldOffset(0x88)]
+ public int LeftStickX;
- ///
- /// Left analogue stick's vertical value, -99 for down, 99 for up.
- ///
- [FieldOffset(0x8C)]
- public int LeftStickY;
+ ///
+ /// Left analogue stick's vertical value, -99 for down, 99 for up.
+ ///
+ [FieldOffset(0x8C)]
+ public int LeftStickY;
- ///
- /// Right analogue stick's horizontal value, -99 for left, 99 for right.
- ///
- [FieldOffset(0x90)]
- public int RightStickX;
+ ///
+ /// Right analogue stick's horizontal value, -99 for left, 99 for right.
+ ///
+ [FieldOffset(0x90)]
+ public int RightStickX;
- ///
- /// Right analogue stick's vertical value, -99 for down, 99 for up.
- ///
- [FieldOffset(0x94)]
- public int RightStickY;
+ ///
+ /// Right analogue stick's vertical value, -99 for down, 99 for up.
+ ///
+ [FieldOffset(0x94)]
+ public int RightStickY;
- ///
- /// Raw input, set the whole time while a button is held. See for the mapping.
- ///
- ///
- /// This is a bitfield.
- ///
- [FieldOffset(0x98)]
- public ushort ButtonsRaw;
+ ///
+ /// Raw input, set the whole time while a button is held. See for the mapping.
+ ///
+ ///
+ /// This is a bitfield.
+ ///
+ [FieldOffset(0x98)]
+ public ushort ButtonsRaw;
- ///
- /// Button pressed, set once when the button is pressed. See for the mapping.
- ///
- ///
- /// This is a bitfield.
- ///
- [FieldOffset(0x9C)]
- public ushort ButtonsPressed;
+ ///
+ /// Button pressed, set once when the button is pressed. See for the mapping.
+ ///
+ ///
+ /// This is a bitfield.
+ ///
+ [FieldOffset(0x9C)]
+ public ushort ButtonsPressed;
- ///
- /// Button released input, set once right after the button is not hold anymore. See for the mapping.
- ///
- ///
- /// This is a bitfield.
- ///
- [FieldOffset(0xA0)]
- public ushort ButtonsReleased;
+ ///
+ /// Button released input, set once right after the button is not hold anymore. See for the mapping.
+ ///
+ ///
+ /// This is a bitfield.
+ ///
+ [FieldOffset(0xA0)]
+ public ushort ButtonsReleased;
- ///
- /// Repeatedly emits the held button input in fixed intervals. See for the mapping.
- ///
- ///
- /// This is a bitfield.
- ///
- [FieldOffset(0xA4)]
- public ushort ButtonsRepeat;
- }
+ ///
+ /// Repeatedly emits the held button input in fixed intervals. See for the mapping.
+ ///
+ ///
+ /// This is a bitfield.
+ ///
+ [FieldOffset(0xA4)]
+ public ushort ButtonsRepeat;
}
diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
index bd4cadbc8..c72e9c1de 100644
--- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs
+++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
@@ -6,249 +6,248 @@ using Dalamud.IoC.Internal;
using ImGuiNET;
using Serilog;
-namespace Dalamud.Game.ClientState.GamePad
+namespace Dalamud.Game.ClientState.GamePad;
+
+///
+/// Exposes the game gamepad state to dalamud.
+///
+/// Will block game's gamepad input if is set.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public unsafe class GamepadState : IDisposable, IServiceType
{
- ///
- /// Exposes the game gamepad state to dalamud.
- ///
- /// Will block game's gamepad input if is set.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public unsafe class GamepadState : IDisposable, IServiceType
+ private readonly Hook gamepadPoll;
+
+ private bool isDisposed;
+
+ private int leftStickX;
+ private int leftStickY;
+ private int rightStickX;
+ private int rightStickY;
+
+ [ServiceManager.ServiceConstructor]
+ private GamepadState(ClientState clientState)
{
- private readonly Hook gamepadPoll;
+ var resolver = clientState.AddressResolver;
+ Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
+ this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
+ }
- private bool isDisposed;
+ private delegate int ControllerPoll(IntPtr controllerInput);
- private int leftStickX;
- private int leftStickY;
- private int rightStickX;
- private int rightStickY;
+ ///
+ /// Gets the pointer to the current instance of the GamepadInput struct.
+ ///
+ public IntPtr GamepadInputAddress { get; private set; }
- [ServiceManager.ServiceConstructor]
- private GamepadState(ClientState clientState)
+ ///
+ /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
+
+ ///
+ /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
+
+ ///
+ /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsPressed { get; private set; }
+
+ ///
+ /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsRaw { get; private set; }
+
+ ///
+ /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsReleased { get; private set; }
+
+ ///
+ /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsRepeat { get; private set; }
+
+ ///
+ /// Gets or sets a value indicating whether detour should block gamepad input for game.
+ ///
+ /// Ideally, we would use
+ /// (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.
+ ///
+ internal bool NavEnableGamepad { get; set; }
+
+ ///
+ /// Gets whether has been pressed.
+ ///
+ /// Only true on first frame of the press.
+ /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
+ ///
+ /// The button to check for.
+ /// 1 if pressed, 0 otherwise.
+ public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Gets whether is being pressed.
+ ///
+ /// True in intervals if button is held down.
+ /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
+ ///
+ /// The button to check for.
+ /// 1 if still pressed during interval, 0 otherwise or in between intervals.
+ public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Gets whether has been released.
+ ///
+ /// Only true the frame after release.
+ /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
+ ///
+ /// The button to check for.
+ /// 1 if released, 0 otherwise.
+ public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Gets the raw state of .
+ ///
+ /// Is set the entire time a button is pressed down.
+ ///
+ /// The button to check for.
+ /// 1 the whole time button is pressed, 0 otherwise.
+ public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Disposes this instance, alongside its hooks.
+ ///
+ void IDisposable.Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ [ServiceManager.CallWhenServicesReady]
+ private void ContinueConstruction()
+ {
+ this.gamepadPoll.Enable();
+ }
+
+ private int GamepadPollDetour(IntPtr gamepadInput)
+ {
+ var original = this.gamepadPoll.Original(gamepadInput);
+ try
{
- var resolver = clientState.AddressResolver;
- Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
- this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
- }
+ this.GamepadInputAddress = gamepadInput;
+ var input = (GamepadInput*)gamepadInput;
+ this.leftStickX = input->LeftStickX;
+ this.leftStickY = input->LeftStickY;
+ this.rightStickX = input->RightStickX;
+ this.rightStickY = input->RightStickY;
+ this.ButtonsRaw = input->ButtonsRaw;
+ this.ButtonsPressed = input->ButtonsPressed;
+ this.ButtonsReleased = input->ButtonsReleased;
+ this.ButtonsRepeat = input->ButtonsRepeat;
- private delegate int ControllerPoll(IntPtr controllerInput);
-
- ///
- /// Gets the pointer to the current instance of the GamepadInput struct.
- ///
- public IntPtr GamepadInputAddress { get; private set; }
-
- ///
- /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
-
- ///
- /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
-
- ///
- /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
-
- ///
- /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
-
- ///
- /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
-
- ///
- /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
-
- ///
- /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
-
- ///
- /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
- ///
- public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
-
- ///
- /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping.
- ///
- /// Exposed internally for Debug Data window.
- ///
- internal ushort ButtonsPressed { get; private set; }
-
- ///
- /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping.
- ///
- /// Exposed internally for Debug Data window.
- ///
- internal ushort ButtonsRaw { get; private set; }
-
- ///
- /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping.
- ///
- /// Exposed internally for Debug Data window.
- ///
- internal ushort ButtonsReleased { get; private set; }
-
- ///
- /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping.
- ///
- /// Exposed internally for Debug Data window.
- ///
- internal ushort ButtonsRepeat { get; private set; }
-
- ///
- /// Gets or sets a value indicating whether detour should block gamepad input for game.
- ///
- /// Ideally, we would use
- /// (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.
- ///
- internal bool NavEnableGamepad { get; set; }
-
- ///
- /// Gets whether has been pressed.
- ///
- /// Only true on first frame of the press.
- /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
- ///
- /// The button to check for.
- /// 1 if pressed, 0 otherwise.
- public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
-
- ///
- /// Gets whether is being pressed.
- ///
- /// True in intervals if button is held down.
- /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
- ///
- /// The button to check for.
- /// 1 if still pressed during interval, 0 otherwise or in between intervals.
- public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
-
- ///
- /// Gets whether has been released.
- ///
- /// Only true the frame after release.
- /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
- ///
- /// The button to check for.
- /// 1 if released, 0 otherwise.
- public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
-
- ///
- /// Gets the raw state of .
- ///
- /// Is set the entire time a button is pressed down.
- ///
- /// The button to check for.
- /// 1 the whole time button is pressed, 0 otherwise.
- public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
-
- ///
- /// Disposes this instance, alongside its hooks.
- ///
- void IDisposable.Dispose()
- {
- this.Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.gamepadPoll.Enable();
- }
-
- private int GamepadPollDetour(IntPtr gamepadInput)
- {
- var original = this.gamepadPoll.Original(gamepadInput);
- try
+ if (this.NavEnableGamepad)
{
- this.GamepadInputAddress = gamepadInput;
- var input = (GamepadInput*)gamepadInput;
- this.leftStickX = input->LeftStickX;
- this.leftStickY = input->LeftStickY;
- this.rightStickX = input->RightStickX;
- this.rightStickY = input->RightStickY;
- this.ButtonsRaw = input->ButtonsRaw;
- this.ButtonsPressed = input->ButtonsPressed;
- this.ButtonsReleased = input->ButtonsReleased;
- this.ButtonsRepeat = input->ButtonsRepeat;
+ input->LeftStickX = 0;
+ input->LeftStickY = 0;
+ input->RightStickX = 0;
+ input->RightStickY = 0;
- if (this.NavEnableGamepad)
- {
- input->LeftStickX = 0;
- input->LeftStickY = 0;
- input->RightStickX = 0;
- input->RightStickY = 0;
-
- // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
- // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
- // It does block, however, all input to the game.
- // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
- // and the digipad (in some situations, but thankfully not in menus) functional.
- // We can either:
- // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
- // (b) ignore it as so far it seems only a 'visual' error
- // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
- // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
- // because of the other blocked input)
- // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on
- // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
- // This is debatable.
- // ImGui itself does not care either way as it uses the Raw values and does its own state handling.
- const ushort deletionMask = (ushort)(~GamepadButtons.L2
- & ~GamepadButtons.R2
- & ~GamepadButtons.DpadDown
- & ~GamepadButtons.DpadLeft
- & ~GamepadButtons.DpadUp
- & ~GamepadButtons.DpadRight);
- input->ButtonsRaw &= deletionMask;
- input->ButtonsPressed = 0;
- input->ButtonsReleased = 0;
- input->ButtonsRepeat = 0;
- return 0;
- }
-
- // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the
- // original, zero or do the work adjusting the bits.
- return original;
- }
- catch (Exception e)
- {
- Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!");
-
- // NOTE (Chiv) Explicitly deactivate on error
- ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
- return original;
- }
- }
-
- private void Dispose(bool disposing)
- {
- if (this.isDisposed) return;
- if (disposing)
- {
- this.gamepadPoll?.Disable();
- this.gamepadPoll?.Dispose();
+ // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
+ // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
+ // It does block, however, all input to the game.
+ // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
+ // and the digipad (in some situations, but thankfully not in menus) functional.
+ // We can either:
+ // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
+ // (b) ignore it as so far it seems only a 'visual' error
+ // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
+ // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
+ // because of the other blocked input)
+ // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on
+ // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
+ // This is debatable.
+ // ImGui itself does not care either way as it uses the Raw values and does its own state handling.
+ const ushort deletionMask = (ushort)(~GamepadButtons.L2
+ & ~GamepadButtons.R2
+ & ~GamepadButtons.DpadDown
+ & ~GamepadButtons.DpadLeft
+ & ~GamepadButtons.DpadUp
+ & ~GamepadButtons.DpadRight);
+ input->ButtonsRaw &= deletionMask;
+ input->ButtonsPressed = 0;
+ input->ButtonsReleased = 0;
+ input->ButtonsRepeat = 0;
+ return 0;
}
- this.isDisposed = true;
+ // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the
+ // original, zero or do the work adjusting the bits.
+ return original;
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!");
+
+ // NOTE (Chiv) Explicitly deactivate on error
+ ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
+ return original;
}
}
+
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed) return;
+ if (disposing)
+ {
+ this.gamepadPoll?.Disable();
+ this.gamepadPoll?.Dispose();
+ }
+
+ this.isDisposed = true;
+ }
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs
index 28d34d55e..9c4d40700 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs
@@ -1,28 +1,27 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// MNK Beast Chakra types.
+///
+public enum BeastChakra : byte
{
///
- /// MNK Beast Chakra types.
+ /// No card.
///
- public enum BeastChakra : byte
- {
- ///
- /// No card.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// The Coeurl chakra.
- ///
- COEURL = 1,
+ ///
+ /// The Coeurl chakra.
+ ///
+ COEURL = 1,
- ///
- /// The Opo-Opo chakra.
- ///
- OPOOPO = 2,
+ ///
+ /// The Opo-Opo chakra.
+ ///
+ OPOOPO = 2,
- ///
- /// The Raptor chakra.
- ///
- RAPTOR = 3,
- }
+ ///
+ /// The Raptor chakra.
+ ///
+ RAPTOR = 3,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs
index 02e064b12..1b367a93e 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs
@@ -1,53 +1,52 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// AST Arcanum (card) types.
+///
+public enum CardType : byte
{
///
- /// AST Arcanum (card) types.
+ /// No card.
///
- public enum CardType : byte
- {
- ///
- /// No card.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// The Balance card.
- ///
- BALANCE = 1,
+ ///
+ /// The Balance card.
+ ///
+ BALANCE = 1,
- ///
- /// The Bole card.
- ///
- BOLE = 2,
+ ///
+ /// The Bole card.
+ ///
+ BOLE = 2,
- ///
- /// The Arrow card.
- ///
- ARROW = 3,
+ ///
+ /// The Arrow card.
+ ///
+ ARROW = 3,
- ///
- /// The Spear card.
- ///
- SPEAR = 4,
+ ///
+ /// The Spear card.
+ ///
+ SPEAR = 4,
- ///
- /// The Ewer card.
- ///
- EWER = 5,
+ ///
+ /// The Ewer card.
+ ///
+ EWER = 5,
- ///
- /// The Spire card.
- ///
- SPIRE = 6,
+ ///
+ /// The Spire card.
+ ///
+ SPIRE = 6,
- ///
- /// The Lord of Crowns card.
- ///
- LORD = 0x70,
+ ///
+ /// The Lord of Crowns card.
+ ///
+ LORD = 0x70,
- ///
- /// The Lady of Crowns card.
- ///
- LADY = 0x80,
- }
+ ///
+ /// The Lady of Crowns card.
+ ///
+ LADY = 0x80,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs
index 9083aad8a..b674d11b8 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs
@@ -1,18 +1,17 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// SCH Dismissed fairy types.
+///
+public enum DismissedFairy : byte
{
///
- /// SCH Dismissed fairy types.
+ /// Dismissed fairy is Eos.
///
- public enum DismissedFairy : byte
- {
- ///
- /// Dismissed fairy is Eos.
- ///
- EOS = 6,
+ EOS = 6,
- ///
- /// Dismissed fairy is Selene.
- ///
- SELENE = 7,
- }
+ ///
+ /// Dismissed fairy is Selene.
+ ///
+ SELENE = 7,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs
index 5f41dde48..e35dcc7f9 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs
@@ -1,33 +1,32 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// SAM Kaeshi types.
+///
+public enum Kaeshi : byte
{
///
- /// SAM Kaeshi types.
+ /// No Kaeshi is active.
///
- public enum Kaeshi : byte
- {
- ///
- /// No Kaeshi is active.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// Kaeshi: Higanbana type.
- ///
- HIGANBANA = 1,
+ ///
+ /// Kaeshi: Higanbana type.
+ ///
+ HIGANBANA = 1,
- ///
- /// Kaeshi: Goken type.
- ///
- GOKEN = 2,
+ ///
+ /// Kaeshi: Goken type.
+ ///
+ GOKEN = 2,
- ///
- /// Kaeshi: Setsugekka type.
- ///
- SETSUGEKKA = 3,
+ ///
+ /// Kaeshi: Setsugekka type.
+ ///
+ SETSUGEKKA = 3,
- ///
- /// Kaeshi: Namikiri type.
- ///
- NAMIKIRI = 4,
- }
+ ///
+ /// Kaeshi: Namikiri type.
+ ///
+ NAMIKIRI = 4,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs
index 71449645f..31ee6dac1 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs
@@ -1,23 +1,22 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// NIN Mudra types.
+///
+public enum Mudras : byte
{
///
- /// NIN Mudra types.
+ /// Ten mudra.
///
- public enum Mudras : byte
- {
- ///
- /// Ten mudra.
- ///
- TEN = 1,
+ TEN = 1,
- ///
- /// Chi mudra.
- ///
- CHI = 2,
+ ///
+ /// Chi mudra.
+ ///
+ CHI = 2,
- ///
- /// Jin mudra.
- ///
- JIN = 3,
- }
+ ///
+ /// Jin mudra.
+ ///
+ JIN = 3,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs
index f84a7e55e..159ecbb26 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs
@@ -1,26 +1,25 @@
using System;
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// MNK Nadi types.
+///
+[Flags]
+public enum Nadi : byte
{
///
- /// MNK Nadi types.
+ /// No card.
///
- [Flags]
- public enum Nadi : byte
- {
- ///
- /// No card.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// The Lunar nadi.
- ///
- LUNAR = 2,
+ ///
+ /// The Lunar nadi.
+ ///
+ LUNAR = 2,
- ///
- /// The Solar nadi.
- ///
- SOLAR = 4,
- }
+ ///
+ /// The Solar nadi.
+ ///
+ SOLAR = 4,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs
index 7aaaca908..248caa396 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs
@@ -1,48 +1,47 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// SMN summoned pet glam types.
+///
+public enum PetGlam : byte
{
///
- /// SMN summoned pet glam types.
+ /// No pet glam.
///
- public enum PetGlam : byte
- {
- ///
- /// No pet glam.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// Emerald carbuncle pet glam.
- ///
- EMERALD = 1,
+ ///
+ /// Emerald carbuncle pet glam.
+ ///
+ EMERALD = 1,
- ///
- /// Topaz carbuncle pet glam.
- ///
- TOPAZ = 2,
+ ///
+ /// Topaz carbuncle pet glam.
+ ///
+ TOPAZ = 2,
- ///
- /// Ruby carbuncle pet glam.
- ///
- RUBY = 3,
+ ///
+ /// Ruby carbuncle pet glam.
+ ///
+ RUBY = 3,
- ///
- /// Normal carbuncle pet glam.
- ///
- CARBUNCLE = 4,
+ ///
+ /// Normal carbuncle pet glam.
+ ///
+ CARBUNCLE = 4,
- ///
- /// Ifrit Egi pet glam.
- ///
- IFRIT = 5,
+ ///
+ /// Ifrit Egi pet glam.
+ ///
+ IFRIT = 5,
- ///
- /// Titan Egi pet glam.
- ///
- TITAN = 6,
+ ///
+ /// Titan Egi pet glam.
+ ///
+ TITAN = 6,
- ///
- /// Garuda Egi pet glam.
- ///
- GARUDA = 7,
- }
+ ///
+ /// Garuda Egi pet glam.
+ ///
+ GARUDA = 7,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs
index 04440dcdc..eacef91fc 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs
@@ -1,28 +1,27 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// AST Divination seal types.
+///
+public enum SealType : byte
{
///
- /// AST Divination seal types.
+ /// No seal.
///
- public enum SealType : byte
- {
- ///
- /// No seal.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// Sun seal.
- ///
- SUN = 1,
+ ///
+ /// Sun seal.
+ ///
+ SUN = 1,
- ///
- /// Moon seal.
- ///
- MOON = 2,
+ ///
+ /// Moon seal.
+ ///
+ MOON = 2,
- ///
- /// Celestial seal.
- ///
- CELESTIAL = 3,
- }
+ ///
+ /// Celestial seal.
+ ///
+ CELESTIAL = 3,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs
index de9e01fc4..bdd98b750 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs
@@ -1,31 +1,30 @@
using System;
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// Samurai Sen types.
+///
+[Flags]
+public enum Sen : byte
{
///
- /// Samurai Sen types.
+ /// No Sen.
///
- [Flags]
- public enum Sen : byte
- {
- ///
- /// No Sen.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// Setsu Sen type.
- ///
- SETSU = 1 << 0,
+ ///
+ /// Setsu Sen type.
+ ///
+ SETSU = 1 << 0,
- ///
- /// Getsu Sen type.
- ///
- GETSU = 1 << 1,
+ ///
+ /// Getsu Sen type.
+ ///
+ GETSU = 1 << 1,
- ///
- /// Ka Sen type.
- ///
- KA = 1 << 2,
- }
+ ///
+ /// Ka Sen type.
+ ///
+ KA = 1 << 2,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs
index b2440640e..c23fbbbff 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs
@@ -1,28 +1,27 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// BRD Song types.
+///
+public enum Song : byte
{
///
- /// BRD Song types.
+ /// No song is active type.
///
- public enum Song : byte
- {
- ///
- /// No song is active type.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// Mage's Ballad type.
- ///
- MAGE = 1,
+ ///
+ /// Mage's Ballad type.
+ ///
+ MAGE = 1,
- ///
- /// Army's Paeon type.
- ///
- ARMY = 2,
+ ///
+ /// Army's Paeon type.
+ ///
+ ARMY = 2,
- ///
- /// The Wanderer's Minuet type.
- ///
- WANDERER = 3,
- }
+ ///
+ /// The Wanderer's Minuet type.
+ ///
+ WANDERER = 3,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs
index aa7e7c8a2..30cc0fff0 100644
--- a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs
@@ -1,18 +1,17 @@
-namespace Dalamud.Game.ClientState.JobGauge.Enums
+namespace Dalamud.Game.ClientState.JobGauge.Enums;
+
+///
+/// SMN summoned pet types.
+///
+public enum SummonPet : byte
{
///
- /// SMN summoned pet types.
+ /// No pet.
///
- public enum SummonPet : byte
- {
- ///
- /// No pet.
- ///
- NONE = 0,
+ NONE = 0,
- ///
- /// The summoned pet Carbuncle.
- ///
- CARBUNCLE = 23,
- }
+ ///
+ /// The summoned pet Carbuncle.
+ ///
+ CARBUNCLE = 23,
}
diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs
index aab5d4991..bf5c4b525 100644
--- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs
+++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs
@@ -7,46 +7,45 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Serilog;
-namespace Dalamud.Game.ClientState.JobGauge
+namespace Dalamud.Game.ClientState.JobGauge;
+
+///
+/// This class converts in-memory Job gauge data to structs.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.BlockingEarlyLoadedService]
+public class JobGauges : IServiceType
{
- ///
- /// This class converts in-memory Job gauge data to structs.
- ///
- [PluginInterface]
- [InterfaceVersion("1.0")]
- [ServiceManager.BlockingEarlyLoadedService]
- public class JobGauges : IServiceType
+ private Dictionary cache = new();
+
+ [ServiceManager.ServiceConstructor]
+ private JobGauges(ClientState clientState)
{
- private Dictionary cache = new();
+ this.Address = clientState.AddressResolver.JobGaugeData;
- [ServiceManager.ServiceConstructor]
- private JobGauges(ClientState clientState)
+ Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
+ }
+
+ ///
+ /// Gets the address of the JobGauge data.
+ ///
+ public IntPtr Address { get; }
+
+ ///
+ /// Get the JobGauge for a given job.
+ ///
+ /// A JobGauge struct from ClientState.Structs.JobGauge.
+ /// A JobGauge.
+ public T Get() where T : JobGaugeBase
+ {
+ // This is cached to mitigate the effects of using activator for instantiation.
+ // Since the gauge itself reads from live memory, there isn't much downside to doing this.
+ if (!this.cache.TryGetValue(typeof(T), out var gauge))
{
- this.Address = clientState.AddressResolver.JobGaugeData;
-
- Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
+ gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null);
}
- ///
- /// Gets the address of the JobGauge data.
- ///
- public IntPtr Address { get; }
-
- ///
- /// Get the JobGauge for a given job.
- ///
- /// A JobGauge struct from ClientState.Structs.JobGauge.
- /// A JobGauge.
- public T Get() where T : JobGaugeBase
- {
- // This is cached to mitigate the effects of using activator for instantiation.
- // Since the gauge itself reads from live memory, there isn't much downside to doing this.
- if (!this.cache.TryGetValue(typeof(T), out var gauge))
- {
- gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null);
- }
-
- return (T)gauge;
- }
+ return (T)gauge;
}
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs
index cb8ed1eb0..4549ff9c9 100644
--- a/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs
@@ -3,44 +3,43 @@ using System.Linq;
using Dalamud.Game.ClientState.JobGauge.Enums;
-namespace Dalamud.Game.ClientState.JobGauge.Types
+namespace Dalamud.Game.ClientState.JobGauge.Types;
+
+///
+/// In-memory AST job gauge.
+///
+public unsafe class ASTGauge : JobGaugeBase
{
///
- /// In-memory AST job gauge.
+ /// Initializes a new instance of the class.
///
- public unsafe class ASTGauge : JobGaugeBase
+ /// Address of the job gauge.
+ internal ASTGauge(IntPtr address)
+ : base(address)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the job gauge.
- internal ASTGauge(IntPtr address)
- : base(address)
- {
- }
-
- ///
- /// Gets the currently drawn .
- ///
- /// Currently drawn .
- public CardType DrawnCard => (CardType)(this.Struct->Card & 0xF);
-
- ///
- /// Gets the currently drawn crown .
- ///
- /// Currently drawn crown .
- public CardType DrawnCrownCard => this.Struct->Card - this.DrawnCard;
-
- ///
- /// Gets the s currently active.
- ///
- public SealType[] Seals => this.Struct->CurrentSeals.Select(seal => (SealType)seal).ToArray();
-
- ///
- /// Check if a is currently active on the divination gauge.
- ///
- /// The to check for.
- /// If the given Seal is currently divined.
- public unsafe bool ContainsSeal(SealType seal) => this.Seals.Contains(seal);
}
+
+ ///
+ /// Gets the currently drawn .
+ ///
+ /// Currently drawn .
+ public CardType DrawnCard => (CardType)(this.Struct->Card & 0xF);
+
+ ///
+ /// Gets the currently drawn crown .
+ ///
+ /// Currently drawn crown .
+ public CardType DrawnCrownCard => this.Struct->Card - this.DrawnCard;
+
+ ///
+ /// Gets the s currently active.
+ ///
+ public SealType[] Seals => this.Struct->CurrentSeals.Select(seal => (SealType)seal).ToArray();
+
+ ///
+ /// Check if a is currently active on the divination gauge.
+ ///
+ /// The to check for.
+ /// If the given Seal is currently divined.
+ public unsafe bool ContainsSeal(SealType seal) => this.Seals.Contains(seal);
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs
index e195225d8..4ed8eabe4 100644
--- a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs
@@ -1,73 +1,72 @@
using System;
-namespace Dalamud.Game.ClientState.JobGauge.Types
+namespace Dalamud.Game.ClientState.JobGauge.Types;
+
+///
+/// In-memory BLM job gauge.
+///
+public unsafe class BLMGauge : JobGaugeBase
{
///
- /// In-memory BLM job gauge.
+ /// Initializes a new instance of the class.
///
- public unsafe class BLMGauge : JobGaugeBase
+ /// Address of the job gauge.
+ internal BLMGauge(IntPtr address)
+ : base(address)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the job gauge.
- internal BLMGauge(IntPtr address)
- : base(address)
- {
- }
-
- ///
- /// Gets the time remaining for the Enochian time in milliseconds.
- ///
- public short EnochianTimer => this.Struct->EnochianTimer;
-
- ///
- /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
- ///
- public short ElementTimeRemaining => this.Struct->ElementTimeRemaining;
-
- ///
- /// Gets the number of Polyglot stacks remaining.
- ///
- public byte PolyglotStacks => this.Struct->PolyglotStacks;
-
- ///
- /// Gets the number of Umbral Hearts remaining.
- ///
- public byte UmbralHearts => this.Struct->UmbralHearts;
-
- ///
- /// Gets the amount of Umbral Ice stacks.
- ///
- public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0);
-
- ///
- /// Gets the amount of Astral Fire stacks.
- ///
- public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0);
-
- ///
- /// Gets a value indicating whether or not the player is in Umbral Ice.
- ///
- /// true or false.
- public bool InUmbralIce => this.Struct->ElementStance < 0;
-
- ///
- /// Gets a value indicating whether or not the player is in Astral fire.
- ///
- /// true or false.
- public bool InAstralFire => this.Struct->ElementStance > 0;
-
- ///
- /// Gets a value indicating whether or not Enochian is active.
- ///
- /// true or false.
- public bool IsEnochianActive => this.Struct->EnochianActive;
-
- ///
- /// Gets a value indicating whether Paradox is active.
- ///
- /// true or false.
- public bool IsParadoxActive => this.Struct->ParadoxActive;
}
+
+ ///
+ /// Gets the time remaining for the Enochian time in milliseconds.
+ ///
+ public short EnochianTimer => this.Struct->EnochianTimer;
+
+ ///
+ /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
+ ///
+ public short ElementTimeRemaining => this.Struct->ElementTimeRemaining;
+
+ ///
+ /// Gets the number of Polyglot stacks remaining.
+ ///
+ public byte PolyglotStacks => this.Struct->PolyglotStacks;
+
+ ///
+ /// Gets the number of Umbral Hearts remaining.
+ ///
+ public byte UmbralHearts => this.Struct->UmbralHearts;
+
+ ///
+ /// Gets the amount of Umbral Ice stacks.
+ ///
+ public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0);
+
+ ///
+ /// Gets the amount of Astral Fire stacks.
+ ///
+ public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0);
+
+ ///
+ /// Gets a value indicating whether or not the player is in Umbral Ice.
+ ///
+ /// true or false.
+ public bool InUmbralIce => this.Struct->ElementStance < 0;
+
+ ///
+ /// Gets a value indicating whether or not the player is in Astral fire.
+ ///
+ /// true or false.
+ public bool InAstralFire => this.Struct->ElementStance > 0;
+
+ ///
+ /// Gets a value indicating whether or not Enochian is active.
+ ///
+ /// true or false.
+ public bool IsEnochianActive => this.Struct->EnochianActive;
+
+ ///
+ /// Gets a value indicating whether Paradox is active.
+ ///
+ /// true or false.
+ public bool IsParadoxActive => this.Struct->ParadoxActive;
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs
index 006ee907b..1a7f7bd47 100644
--- a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs
@@ -3,94 +3,93 @@ using System;
using Dalamud.Game.ClientState.JobGauge.Enums;
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
-namespace Dalamud.Game.ClientState.JobGauge.Types
+namespace Dalamud.Game.ClientState.JobGauge.Types;
+
+///
+/// In-memory BRD job gauge.
+///
+public unsafe class BRDGauge : JobGaugeBase
{
///
- /// In-memory BRD job gauge.
+ /// Initializes a new instance of the class.
///
- public unsafe class BRDGauge : JobGaugeBase
+ /// Address of the job gauge.
+ internal BRDGauge(IntPtr address)
+ : base(address)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the job gauge.
- internal BRDGauge(IntPtr address)
- : base(address)
+ }
+
+ ///
+ /// Gets the current song timer in milliseconds.
+ ///
+ public ushort SongTimer => this.Struct->SongTimer;
+
+ ///
+ /// Gets the amount of Repertoire accumulated.
+ ///
+ public byte Repertoire => this.Struct->Repertoire;
+
+ ///
+ /// Gets the amount of Soul Voice accumulated.
+ ///
+ public byte SoulVoice => this.Struct->SoulVoice;
+
+ ///
+ /// Gets the type of song that is active.
+ ///
+ public Song Song
+ {
+ get
{
+ if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuet))
+ return Song.WANDERER;
+
+ if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon))
+ return Song.ARMY;
+
+ if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad))
+ return Song.MAGE;
+
+ return Song.NONE;
}
+ }
- ///
- /// Gets the current song timer in milliseconds.
- ///
- public ushort SongTimer => this.Struct->SongTimer;
-
- ///
- /// Gets the amount of Repertoire accumulated.
- ///
- public byte Repertoire => this.Struct->Repertoire;
-
- ///
- /// Gets the amount of Soul Voice accumulated.
- ///
- public byte SoulVoice => this.Struct->SoulVoice;
-
- ///
- /// Gets the type of song that is active.
- ///
- public Song Song
+ ///
+ /// Gets the type of song that was last played.
+ ///
+ public Song LastSong
+ {
+ get
{
- get
- {
- if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuet))
- return Song.WANDERER;
+ if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed))
+ return Song.WANDERER;
- if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon))
- return Song.ARMY;
+ if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed))
+ return Song.ARMY;
- if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad))
- return Song.MAGE;
+ if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed))
+ return Song.MAGE;
- return Song.NONE;
- }
+ return Song.NONE;
}
+ }
- ///
- /// Gets the type of song that was last played.
- ///
- public Song LastSong
+ ///
+ /// Gets the song Coda that are currently active.
+ ///
+ ///
+ /// This will always return an array of size 3, inactive Coda are represented by .
+ ///
+ public Song[] Coda
+ {
+ get
{
- get
+ return new[]
{
- if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed))
- return Song.WANDERER;
-
- if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed))
- return Song.ARMY;
-
- if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed))
- return Song.MAGE;
-
- return Song.NONE;
- }
- }
-
- ///
- /// Gets the song Coda that are currently active.
- ///
- ///
- /// This will always return an array of size 3, inactive Coda are represented by .
- ///
- public Song[] Coda
- {
- get
- {
- return new[]
- {
- this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.MAGE : Song.NONE,
- this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.ARMY : Song.NONE,
- this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.WANDERER : Song.NONE,
- };
- }
+ this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.MAGE : Song.NONE,
+ this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.ARMY : Song.NONE,
+ this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.WANDERER : Song.NONE,
+ };
}
}
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs
index 7ae2b7bca..455c5bca0 100644
--- a/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs
@@ -1,60 +1,59 @@
using System;
-namespace Dalamud.Game.ClientState.JobGauge.Types
+namespace Dalamud.Game.ClientState.JobGauge.Types;
+
+///
+/// In-memory DNC job gauge.
+///
+public unsafe class DNCGauge : JobGaugeBase
{
///
- /// In-memory DNC job gauge.
+ /// Initializes a new instance of the class.
///
- public unsafe class DNCGauge : JobGaugeBase
+ /// Address of the job gauge.
+ internal DNCGauge(IntPtr address)
+ : base(address)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the job gauge.
- internal DNCGauge(IntPtr address)
- : base(address)
- {
- }
-
- ///
- /// Gets the number of feathers available.
- ///
- public byte Feathers => this.Struct->Feathers;
-
- ///
- /// Gets the amount of Espirit available.
- ///
- public byte Esprit => this.Struct->Esprit;
-
- ///
- /// Gets the number of steps completed for the current dance.
- ///
- public byte CompletedSteps => this.Struct->StepIndex;
-
- ///
- /// Gets all the steps in the current dance.
- ///
- public unsafe uint[] Steps
- {
- get
- {
- var arr = new uint[4];
- for (var i = 0; i < 4; i++)
- arr[i] = this.Struct->DanceSteps[i] + 15999u - 1;
- return arr;
- }
- }
-
- ///
- /// Gets the next step in the current dance.
- ///
- /// The next dance step action ID.
- public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1;
-
- ///
- /// Gets a value indicating whether the player is dancing or not.
- ///
- /// true or false.
- public bool IsDancing => this.Struct->DanceSteps[0] != 0;
}
+
+ ///
+ /// Gets the number of feathers available.
+ ///
+ public byte Feathers => this.Struct->Feathers;
+
+ ///
+ /// Gets the amount of Espirit available.
+ ///
+ public byte Esprit => this.Struct->Esprit;
+
+ ///
+ /// Gets the number of steps completed for the current dance.
+ ///
+ public byte CompletedSteps => this.Struct->StepIndex;
+
+ ///
+ /// Gets all the steps in the current dance.
+ ///
+ public unsafe uint[] Steps
+ {
+ get
+ {
+ var arr = new uint[4];
+ for (var i = 0; i < 4; i++)
+ arr[i] = this.Struct->DanceSteps[i] + 15999u - 1;
+ return arr;
+ }
+ }
+
+ ///
+ /// Gets the next step in the current dance.
+ ///
+ /// The next dance step action ID.
+ public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1;
+
+ ///
+ /// Gets a value indicating whether the player is dancing or not.
+ ///
+ /// true or false.
+ public bool IsDancing => this.Struct->DanceSteps[0] != 0;
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs
index 1003d2cd5..39e39b41b 100644
--- a/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs
@@ -1,39 +1,38 @@
using System;
-namespace Dalamud.Game.ClientState.JobGauge.Types
+namespace Dalamud.Game.ClientState.JobGauge.Types;
+
+///
+/// In-memory DRG job gauge.
+///
+public unsafe class DRGGauge : JobGaugeBase
{
///
- /// In-memory DRG job gauge.
+ /// Initializes a new instance of the class.
///
- public unsafe class DRGGauge : JobGaugeBase
+ /// Address of the job gauge.
+ internal DRGGauge(IntPtr address)
+ : base(address)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the job gauge.
- internal DRGGauge(IntPtr address)
- : base(address)
- {
- }
-
- ///
- /// Gets the time remaining for Life of the Dragon in milliseconds.
- ///
- public short LOTDTimer => this.Struct->LotdTimer;
-
- ///
- /// Gets a value indicating whether Life of the Dragon is active.
- ///
- public bool IsLOTDActive => this.Struct->LotdState == 2;
-
- ///
- /// Gets the count of eyes opened during Blood of the Dragon.
- ///
- public byte EyeCount => this.Struct->EyeCount;
-
- ///
- /// Gets the amount of Firstminds' Focus available.
- ///
- public byte FirstmindsFocusCount => this.Struct->FirstmindsFocusCount;
}
+
+ ///
+ /// Gets the time remaining for Life of the Dragon in milliseconds.
+ ///
+ public short LOTDTimer => this.Struct->LotdTimer;
+
+ ///
+ /// Gets a value indicating whether Life of the Dragon is active.
+ ///
+ public bool IsLOTDActive => this.Struct->LotdState == 2;
+
+ ///
+ /// Gets the count of eyes opened during Blood of the Dragon.
+ ///
+ public byte EyeCount => this.Struct->EyeCount;
+
+ ///
+ /// Gets the amount of Firstminds' Focus available.
+ ///
+ public byte FirstmindsFocusCount => this.Struct->FirstmindsFocusCount;
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs
index d903c0f68..25a245ac6 100644
--- a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs
@@ -1,40 +1,39 @@
using System;
-namespace Dalamud.Game.ClientState.JobGauge.Types
+namespace Dalamud.Game.ClientState.JobGauge.Types;
+
+///
+/// In-memory DRK job gauge.
+///
+public unsafe class DRKGauge : JobGaugeBase
{
///
- /// In-memory DRK job gauge.
+ /// Initializes a new instance of the class.
///
- public unsafe class DRKGauge : JobGaugeBase
+ /// Address of the job gauge.
+ internal DRKGauge(IntPtr address)
+ : base(address)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the job gauge.
- internal DRKGauge(IntPtr address)
- : base(address)
- {
- }
-
- ///
- /// Gets the amount of blood accumulated.
- ///
- public byte Blood => this.Struct->Blood;
-
- ///
- /// Gets the Darkside time remaining in milliseconds.
- ///
- public ushort DarksideTimeRemaining => this.Struct->DarksideTimer;
-
- ///
- /// Gets the Shadow time remaining in milliseconds.
- ///
- public ushort ShadowTimeRemaining => this.Struct->ShadowTimer;
-
- ///
- /// Gets a value indicating whether the player has Dark Arts or not.
- ///
- /// true or false.
- public bool HasDarkArts => this.Struct->DarkArtsState > 0;
}
+
+ ///
+ /// Gets the amount of blood accumulated.
+ ///
+ public byte Blood => this.Struct->Blood;
+
+ ///
+ /// Gets the Darkside time remaining in milliseconds.
+ ///
+ public ushort DarksideTimeRemaining => this.Struct->DarksideTimer;
+
+ ///
+ /// Gets the Shadow time remaining in milliseconds.
+ ///
+ public ushort ShadowTimeRemaining => this.Struct->ShadowTimer;
+
+ ///
+ /// Gets a value indicating whether the player has Dark Arts or not.
+ ///
+ /// true or false.
+ public bool HasDarkArts => this.Struct->DarkArtsState > 0;
}
diff --git a/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs
index dc934d8f4..4acc9a712 100644
--- a/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs
+++ b/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs
@@ -1,34 +1,33 @@
using System;
-namespace Dalamud.Game.ClientState.JobGauge.Types
+namespace Dalamud.Game.ClientState.JobGauge.Types;
+
+///
+/// In-memory GNB job gauge.
+///
+public unsafe class GNBGauge : JobGaugeBase
{
///
- /// In-memory GNB job gauge.
+ /// Initializes a new instance of the