mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
refactor(Dalamud): switch to file-scoped namespaces
This commit is contained in:
parent
13cf3d93dc
commit
b5f34c3199
325 changed files with 45878 additions and 46218 deletions
|
|
@ -1,28 +1,27 @@
|
|||
namespace Dalamud
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
/// Enum describing the language the game loads in.
|
||||
/// </summary>
|
||||
public enum ClientLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum describing the language the game loads in.
|
||||
/// Indicating a Japanese game client.
|
||||
/// </summary>
|
||||
public enum ClientLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicating a Japanese game client.
|
||||
/// </summary>
|
||||
Japanese,
|
||||
Japanese,
|
||||
|
||||
/// <summary>
|
||||
/// Indicating an English game client.
|
||||
/// </summary>
|
||||
English,
|
||||
/// <summary>
|
||||
/// Indicating an English game client.
|
||||
/// </summary>
|
||||
English,
|
||||
|
||||
/// <summary>
|
||||
/// Indicating a German game client.
|
||||
/// </summary>
|
||||
German,
|
||||
/// <summary>
|
||||
/// Indicating a German game client.
|
||||
/// </summary>
|
||||
German,
|
||||
|
||||
/// <summary>
|
||||
/// Indicating a French game client.
|
||||
/// </summary>
|
||||
French,
|
||||
}
|
||||
/// <summary>
|
||||
/// Indicating a French game client.
|
||||
/// </summary>
|
||||
French,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="ClientLanguage"/> class.
|
||||
/// </summary>
|
||||
public static class ClientLanguageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="ClientLanguage"/> class.
|
||||
/// Converts a Dalamud ClientLanguage to the corresponding Lumina variant.
|
||||
/// </summary>
|
||||
public static class ClientLanguageExtensions
|
||||
/// <param name="language">Langauge to convert.</param>
|
||||
/// <returns>Converted langauge.</returns>
|
||||
public static Lumina.Data.Language ToLumina(this ClientLanguage language)
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a Dalamud ClientLanguage to the corresponding Lumina variant.
|
||||
/// </summary>
|
||||
/// <param name="language">Langauge to convert.</param>
|
||||
/// <returns>Converted langauge.</returns>
|
||||
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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
namespace Dalamud.Configuration
|
||||
namespace Dalamud.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration to store settings for a dalamud plugin.
|
||||
/// </summary>
|
||||
public interface IPluginConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration to store settings for a dalamud plugin.
|
||||
/// Gets or sets configuration version.
|
||||
/// </summary>
|
||||
public interface IPluginConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets configuration version.
|
||||
/// </summary>
|
||||
int Version { get; set; }
|
||||
}
|
||||
int Version { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,268 +8,267 @@ using Newtonsoft.Json;
|
|||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Dalamud.Configuration.Internal
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing Dalamud settings.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal sealed class DalamudConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing Dalamud settings.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal sealed class DalamudConfiguration
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for the <see cref="DalamudConfiguration.DalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
|
||||
/// </summary>
|
||||
/// <param name="dalamudConfiguration">The current dalamud configuration.</param>
|
||||
public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Event that occurs when dalamud configuration is saved.
|
||||
/// </summary>
|
||||
public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of muted works.
|
||||
/// </summary>
|
||||
public List<string> BadWords { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
||||
/// </summary>
|
||||
public bool DutyFinderTaskbarFlash { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
|
||||
/// </summary>
|
||||
public bool DutyFinderChatMessage { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language code to load Dalamud localization with.
|
||||
/// </summary>
|
||||
public string LanguageOverride { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last loaded Dalamud version.
|
||||
/// </summary>
|
||||
public string LastVersion { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last loaded Dalamud version.
|
||||
/// </summary>
|
||||
public string LastChangelogMajorMinor { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the chat type used by default for plugin messages.
|
||||
/// </summary>
|
||||
public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin testing builds should be shown.
|
||||
/// </summary>
|
||||
public bool DoPluginTest { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not Dalamud testing builds should be used.
|
||||
/// </summary>
|
||||
public bool DoDalamudTest { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not XL should download the Dalamud .NET runtime.
|
||||
/// </summary>
|
||||
public bool DoDalamudRuntime { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of custom repos.
|
||||
/// </summary>
|
||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of hidden plugins.
|
||||
/// </summary>
|
||||
public List<string> HiddenPluginInternalName { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of seen plugins.
|
||||
/// </summary>
|
||||
public List<string> SeenPluginInternalName { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the global UI scale.
|
||||
/// </summary>
|
||||
public float GlobalUiScale { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden.
|
||||
/// </summary>
|
||||
public bool ToggleUiHide { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
|
||||
/// </summary>
|
||||
public bool ToggleUiHideDuringCutscenes { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
|
||||
/// </summary>
|
||||
public bool ToggleUiHideDuringGpose { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
|
||||
/// </summary>
|
||||
public bool PrintPluginsWelcomeMsg { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugins should be auto-updated.
|
||||
/// </summary>
|
||||
public bool AutoUpdatePlugins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
|
||||
/// </summary>
|
||||
public bool DoButtonsSystemMenu { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default Dalamud debug log level on startup.
|
||||
/// </summary>
|
||||
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
|
||||
/// </summary>
|
||||
public bool LogAutoScroll { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the debug log should open at startup.
|
||||
/// </summary>
|
||||
public bool LogOpenAtStartup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
|
||||
/// </summary>
|
||||
public bool AssertsEnabledAtStartup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
|
||||
/// </summary>
|
||||
public bool IsDocking { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether viewports should always be disabled.
|
||||
/// </summary>
|
||||
public bool IsDisableViewport { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
|
||||
/// </summary>
|
||||
public bool IsGamepadNavigationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not focus management is enabled.
|
||||
/// </summary>
|
||||
public bool IsFocusManagementEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
|
||||
/// </summary>
|
||||
public bool IsAntiAntiDebugEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of beta to download when <see cref="DoDalamudTest"/> is set to true.
|
||||
/// </summary>
|
||||
public string DalamudBetaKind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not all plugins, regardless of API level, should be loaded.
|
||||
/// </summary>
|
||||
public bool LoadAllApiLevels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not banned plugins should be loaded.
|
||||
/// </summary>
|
||||
public bool LoadBannedPlugins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool PluginSafeMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of saved styles.
|
||||
/// </summary>
|
||||
[JsonProperty("SavedStyles")]
|
||||
public List<StyleModelV1>? SavedStylesOld { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of saved styles.
|
||||
/// </summary>
|
||||
[JsonProperty("SavedStylesVersioned")]
|
||||
public List<StyleModel>? SavedStyles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the currently chosen style.
|
||||
/// </summary>
|
||||
public string ChosenStyle { get; set; } = "Dalamud Standard";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled.
|
||||
/// </summary>
|
||||
public bool DisableRmtFiltering { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Load a configuration from the provided path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to load the configuration file from.</param>
|
||||
/// <returns>The deserialized configuration file.</returns>
|
||||
public static DalamudConfiguration Load(string path)
|
||||
{
|
||||
DalamudConfiguration deserialized;
|
||||
try
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All,
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
private string configPath;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for the <see cref="DalamudConfiguration.DalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
|
||||
/// </summary>
|
||||
/// <param name="dalamudConfiguration">The current dalamud configuration.</param>
|
||||
public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Event that occurs when dalamud configuration is saved.
|
||||
/// </summary>
|
||||
public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of muted works.
|
||||
/// </summary>
|
||||
public List<string> BadWords { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
||||
/// </summary>
|
||||
public bool DutyFinderTaskbarFlash { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
|
||||
/// </summary>
|
||||
public bool DutyFinderChatMessage { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language code to load Dalamud localization with.
|
||||
/// </summary>
|
||||
public string LanguageOverride { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last loaded Dalamud version.
|
||||
/// </summary>
|
||||
public string LastVersion { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last loaded Dalamud version.
|
||||
/// </summary>
|
||||
public string LastChangelogMajorMinor { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the chat type used by default for plugin messages.
|
||||
/// </summary>
|
||||
public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin testing builds should be shown.
|
||||
/// </summary>
|
||||
public bool DoPluginTest { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not Dalamud testing builds should be used.
|
||||
/// </summary>
|
||||
public bool DoDalamudTest { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not XL should download the Dalamud .NET runtime.
|
||||
/// </summary>
|
||||
public bool DoDalamudRuntime { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of custom repos.
|
||||
/// </summary>
|
||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of hidden plugins.
|
||||
/// </summary>
|
||||
public List<string> HiddenPluginInternalName { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of seen plugins.
|
||||
/// </summary>
|
||||
public List<string> SeenPluginInternalName { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the global UI scale.
|
||||
/// </summary>
|
||||
public float GlobalUiScale { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden.
|
||||
/// </summary>
|
||||
public bool ToggleUiHide { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
|
||||
/// </summary>
|
||||
public bool ToggleUiHideDuringCutscenes { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
|
||||
/// </summary>
|
||||
public bool ToggleUiHideDuringGpose { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
|
||||
/// </summary>
|
||||
public bool PrintPluginsWelcomeMsg { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not plugins should be auto-updated.
|
||||
/// </summary>
|
||||
public bool AutoUpdatePlugins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
|
||||
/// </summary>
|
||||
public bool DoButtonsSystemMenu { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default Dalamud debug log level on startup.
|
||||
/// </summary>
|
||||
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
|
||||
/// </summary>
|
||||
public bool LogAutoScroll { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the debug log should open at startup.
|
||||
/// </summary>
|
||||
public bool LogOpenAtStartup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
|
||||
/// </summary>
|
||||
public bool AssertsEnabledAtStartup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
|
||||
/// </summary>
|
||||
public bool IsDocking { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether viewports should always be disabled.
|
||||
/// </summary>
|
||||
public bool IsDisableViewport { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
|
||||
/// </summary>
|
||||
public bool IsGamepadNavigationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not focus management is enabled.
|
||||
/// </summary>
|
||||
public bool IsFocusManagementEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
|
||||
/// </summary>
|
||||
public bool IsAntiAntiDebugEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of beta to download when <see cref="DoDalamudTest"/> is set to true.
|
||||
/// </summary>
|
||||
public string DalamudBetaKind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not all plugins, regardless of API level, should be loaded.
|
||||
/// </summary>
|
||||
public bool LoadAllApiLevels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not banned plugins should be loaded.
|
||||
/// </summary>
|
||||
public bool LoadBannedPlugins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool PluginSafeMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of saved styles.
|
||||
/// </summary>
|
||||
[JsonProperty("SavedStyles")]
|
||||
public List<StyleModelV1>? SavedStylesOld { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of saved styles.
|
||||
/// </summary>
|
||||
[JsonProperty("SavedStylesVersioned")]
|
||||
public List<StyleModel>? SavedStyles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the currently chosen style.
|
||||
/// </summary>
|
||||
public string ChosenStyle { get; set; } = "Dalamud Standard";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled.
|
||||
/// </summary>
|
||||
public bool DisableRmtFiltering { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Load a configuration from the provided path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to load the configuration file from.</param>
|
||||
/// <returns>The deserialized configuration file.</returns>
|
||||
public static DalamudConfiguration Load(string path)
|
||||
deserialized = JsonConvert.DeserializeObject<DalamudConfiguration>(File.ReadAllText(path), SerializerSettings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DalamudConfiguration deserialized;
|
||||
try
|
||||
{
|
||||
deserialized = JsonConvert.DeserializeObject<DalamudConfiguration>(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;
|
||||
Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path);
|
||||
deserialized = new DalamudConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the configuration at the path it was loaded from.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
||||
this.DalamudConfigurationSaved?.Invoke(this);
|
||||
}
|
||||
deserialized.configPath = path;
|
||||
|
||||
return deserialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the configuration at the path it was loaded from.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
||||
this.DalamudConfigurationSaved?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
namespace Dalamud.Configuration
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Additional locations to load dev plugins from.
|
||||
/// </summary>
|
||||
internal sealed class DevPluginLocationSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional locations to load dev plugins from.
|
||||
/// Gets or sets the dev pluign path.
|
||||
/// </summary>
|
||||
internal sealed class DevPluginLocationSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the dev pluign path.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Clone this object.
|
||||
/// </summary>
|
||||
/// <returns>A shallow copy of this object.</returns>
|
||||
public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
|
||||
}
|
||||
/// <summary>
|
||||
/// Clone this object.
|
||||
/// </summary>
|
||||
/// <returns>A shallow copy of this object.</returns>
|
||||
public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
namespace Dalamud.Configuration.Internal
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Settings for DevPlugins.
|
||||
/// </summary>
|
||||
internal sealed class DevPluginSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings for DevPlugins.
|
||||
/// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
|
||||
/// </summary>
|
||||
internal sealed class DevPluginSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
|
||||
/// </summary>
|
||||
public bool StartOnBoot { get; set; } = true;
|
||||
public bool StartOnBoot { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
||||
/// </summary>
|
||||
public bool AutomaticReloading { get; set; } = false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
||||
/// </summary>
|
||||
public bool AutomaticReloading { get; set; } = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,37 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Configuration.Internal
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Environmental configuration settings.
|
||||
/// </summary>
|
||||
internal class EnvironmentConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Environmental configuration settings.
|
||||
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
|
||||
/// </summary>
|
||||
internal class EnvironmentConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
|
||||
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing.
|
||||
/// </summary>
|
||||
public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER");
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing.
|
||||
/// </summary>
|
||||
public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER");
|
||||
|
||||
private static bool GetEnvironmentVariable(string name)
|
||||
=> bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
|
||||
}
|
||||
private static bool GetEnvironmentVariable(string name)
|
||||
=> bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
namespace Dalamud.Configuration
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Third party repository for dalamud plugins.
|
||||
/// </summary>
|
||||
internal sealed class ThirdPartyRepoSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Third party repository for dalamud plugins.
|
||||
/// Gets or sets the third party repo url.
|
||||
/// </summary>
|
||||
internal sealed class ThirdPartyRepoSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the third party repo url.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a short name for the repo url.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a short name for the repo url.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Clone this object.
|
||||
/// </summary>
|
||||
/// <returns>A shallow copy of this object.</returns>
|
||||
public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
|
||||
}
|
||||
/// <summary>
|
||||
/// Clone this object.
|
||||
/// </summary>
|
||||
/// <returns>A shallow copy of this object.</returns>
|
||||
public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,129 +2,128 @@ using System.IO;
|
|||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Configuration
|
||||
namespace Dalamud.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration to store settings for a dalamud plugin.
|
||||
/// </summary>
|
||||
public sealed class PluginConfigurations
|
||||
{
|
||||
private readonly DirectoryInfo configDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration to store settings for a dalamud plugin.
|
||||
/// Initializes a new instance of the <see cref="PluginConfigurations"/> class.
|
||||
/// </summary>
|
||||
public sealed class PluginConfigurations
|
||||
/// <param name="storageFolder">Directory for storage of plugin configuration files.</param>
|
||||
public PluginConfigurations(string storageFolder)
|
||||
{
|
||||
private readonly DirectoryInfo configDirectory;
|
||||
this.configDirectory = new DirectoryInfo(storageFolder);
|
||||
this.configDirectory.Create();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginConfigurations"/> class.
|
||||
/// </summary>
|
||||
/// <param name="storageFolder">Directory for storage of plugin configuration files.</param>
|
||||
public PluginConfigurations(string storageFolder)
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="config">Plugin configuration.</param>
|
||||
/// <param name="pluginName">Plugin name.</param>
|
||||
public void Save(IPluginConfiguration config, string pluginName)
|
||||
{
|
||||
File.WriteAllText(this.GetConfigFile(pluginName).FullName, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
|
||||
{
|
||||
this.configDirectory = new DirectoryInfo(storageFolder);
|
||||
this.configDirectory.Create();
|
||||
}
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameHandling = TypeNameHandling.Objects,
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="config">Plugin configuration.</param>
|
||||
/// <param name="pluginName">Plugin name.</param>
|
||||
public void Save(IPluginConfiguration config, string pluginName)
|
||||
{
|
||||
File.WriteAllText(this.GetConfigFile(pluginName).FullName, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
|
||||
/// <summary>
|
||||
/// Load plugin configuration.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">Plugin name.</param>
|
||||
/// <returns>Plugin configuration.</returns>
|
||||
public IPluginConfiguration? Load(string pluginName)
|
||||
{
|
||||
var path = this.GetConfigFile(pluginName);
|
||||
|
||||
if (!path.Exists)
|
||||
return null;
|
||||
|
||||
return JsonConvert.DeserializeObject<IPluginConfiguration>(
|
||||
File.ReadAllText(path.FullName),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameHandling = TypeNameHandling.Objects,
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load plugin configuration.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">Plugin name.</param>
|
||||
/// <returns>Plugin configuration.</returns>
|
||||
public IPluginConfiguration? Load(string pluginName)
|
||||
{
|
||||
var path = this.GetConfigFile(pluginName);
|
||||
|
||||
if (!path.Exists)
|
||||
return null;
|
||||
|
||||
return JsonConvert.DeserializeObject<IPluginConfiguration>(
|
||||
File.ReadAllText(path.FullName),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameHandling = TypeNameHandling.Objects,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the configuration file and folder for the specified plugin.
|
||||
/// This will throw an <see cref="IOException"/> if the plugin did not correctly close its handles.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin.</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get plugin directory.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">Plugin name.</param>
|
||||
/// <returns>Plugin directory path.</returns>
|
||||
public string GetDirectory(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = this.GetDirectoryPath(pluginName);
|
||||
if (!path.Exists)
|
||||
{
|
||||
path.Create();
|
||||
}
|
||||
|
||||
return path.FullName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">Plugin Name.</param>
|
||||
/// <typeparam name="T">Configuration Type.</typeparam>
|
||||
/// <returns>Plugin Configuration.</returns>
|
||||
public T LoadForType<T>(string pluginName) where T : IPluginConfiguration
|
||||
{
|
||||
var path = this.GetConfigFile(pluginName);
|
||||
|
||||
return !path.Exists ? default : JsonConvert.DeserializeObject<T>(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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get FileInfo to plugin config file.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">InternalName of the plugin.</param>
|
||||
/// <returns>FileInfo of the config file.</returns>
|
||||
public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
|
||||
|
||||
private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the configuration file and folder for the specified plugin.
|
||||
/// This will throw an <see cref="IOException"/> if the plugin did not correctly close its handles.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin.</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get plugin directory.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">Plugin name.</param>
|
||||
/// <returns>Plugin directory path.</returns>
|
||||
public string GetDirectory(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = this.GetDirectoryPath(pluginName);
|
||||
if (!path.Exists)
|
||||
{
|
||||
path.Create();
|
||||
}
|
||||
|
||||
return path.FullName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">Plugin Name.</param>
|
||||
/// <typeparam name="T">Configuration Type.</typeparam>
|
||||
/// <returns>Plugin Configuration.</returns>
|
||||
public T LoadForType<T>(string pluginName) where T : IPluginConfiguration
|
||||
{
|
||||
var path = this.GetConfigFile(pluginName);
|
||||
|
||||
return !path.Exists ? default : JsonConvert.DeserializeObject<T>(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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get FileInfo to plugin config file.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">InternalName of the plugin.</param>
|
||||
/// <returns>FileInfo of the config file.</returns>
|
||||
public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
|
||||
|
||||
private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,379 +33,378 @@ using Serilog.Events;
|
|||
|
||||
[assembly: InternalsVisibleTo("Dalamud.Test")]
|
||||
|
||||
namespace Dalamud
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
/// The main Dalamud class containing all subsystems.
|
||||
/// </summary>
|
||||
internal sealed class Dalamud : IDisposable
|
||||
{
|
||||
#region Internals
|
||||
|
||||
private readonly ManualResetEvent unloadSignal;
|
||||
private readonly ManualResetEvent finishUnloadSignal;
|
||||
private MonoMod.RuntimeDetour.Hook processMonoHook;
|
||||
private bool hasDisposedPlugins = false;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The main Dalamud class containing all subsystems.
|
||||
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
||||
/// </summary>
|
||||
internal sealed class Dalamud : IDisposable
|
||||
/// <param name="info">DalamudStartInfo instance.</param>
|
||||
/// <param name="loggingLevelSwitch">LoggingLevelSwitch to control Serilog level.</param>
|
||||
/// <param name="finishSignal">Signal signalling shutdown.</param>
|
||||
/// <param name="configuration">The Dalamud configuration.</param>
|
||||
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration)
|
||||
{
|
||||
#region Internals
|
||||
this.ApplyProcessPatch();
|
||||
|
||||
private readonly ManualResetEvent unloadSignal;
|
||||
private readonly ManualResetEvent finishUnloadSignal;
|
||||
private MonoMod.RuntimeDetour.Hook processMonoHook;
|
||||
private bool hasDisposedPlugins = false;
|
||||
Service<Dalamud>.Set(this);
|
||||
Service<DalamudStartInfo>.Set(info);
|
||||
Service<DalamudConfiguration>.Set(configuration);
|
||||
|
||||
#endregion
|
||||
this.LogLevelSwitch = loggingLevelSwitch;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">DalamudStartInfo instance.</param>
|
||||
/// <param name="loggingLevelSwitch">LoggingLevelSwitch to control Serilog level.</param>
|
||||
/// <param name="finishSignal">Signal signalling shutdown.</param>
|
||||
/// <param name="configuration">The Dalamud configuration.</param>
|
||||
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration)
|
||||
this.unloadSignal = new ManualResetEvent(false);
|
||||
this.unloadSignal.Reset();
|
||||
|
||||
this.finishUnloadSignal = finishSignal;
|
||||
this.finishUnloadSignal.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets LoggingLevelSwitch for Dalamud and Plugin logs.
|
||||
/// </summary>
|
||||
internal LoggingLevelSwitch LogLevelSwitch { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets location of stored assets.
|
||||
/// </summary>
|
||||
internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// Runs tier 1 of the Dalamud initialization process.
|
||||
/// </summary>
|
||||
public void LoadTier1()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.ApplyProcessPatch();
|
||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||
|
||||
Service<Dalamud>.Set(this);
|
||||
Service<DalamudStartInfo>.Set(info);
|
||||
Service<DalamudConfiguration>.Set(configuration);
|
||||
Service<ServiceContainer>.Set();
|
||||
|
||||
this.LogLevelSwitch = loggingLevelSwitch;
|
||||
// Initialize the process information.
|
||||
Service<SigScanner>.Set(new SigScanner(true));
|
||||
Service<HookManager>.Set();
|
||||
|
||||
this.unloadSignal = new ManualResetEvent(false);
|
||||
this.unloadSignal.Reset();
|
||||
// Initialize FFXIVClientStructs function resolver
|
||||
FFXIVClientStructs.Resolver.Initialize();
|
||||
Log.Information("[T1] FFXIVClientStructs initialized!");
|
||||
|
||||
this.finishUnloadSignal = finishSignal;
|
||||
this.finishUnloadSignal.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets LoggingLevelSwitch for Dalamud and Plugin logs.
|
||||
/// </summary>
|
||||
internal LoggingLevelSwitch LogLevelSwitch { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets location of stored assets.
|
||||
/// </summary>
|
||||
internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// Runs tier 1 of the Dalamud initialization process.
|
||||
/// </summary>
|
||||
public void LoadTier1()
|
||||
{
|
||||
try
|
||||
{
|
||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||
|
||||
Service<ServiceContainer>.Set();
|
||||
|
||||
// Initialize the process information.
|
||||
Service<SigScanner>.Set(new SigScanner(true));
|
||||
Service<HookManager>.Set();
|
||||
|
||||
// Initialize FFXIVClientStructs function resolver
|
||||
FFXIVClientStructs.Resolver.Initialize();
|
||||
Log.Information("[T1] FFXIVClientStructs initialized!");
|
||||
|
||||
// Initialize game subsystem
|
||||
var framework = Service<Framework>.Set();
|
||||
Log.Information("[T1] Framework OK!");
|
||||
// Initialize game subsystem
|
||||
var framework = Service<Framework>.Set();
|
||||
Log.Information("[T1] Framework OK!");
|
||||
|
||||
#if DEBUG
|
||||
Service<TaskTracker>.Set();
|
||||
Log.Information("[T1] TaskTracker OK!");
|
||||
Service<TaskTracker>.Set();
|
||||
Log.Information("[T1] TaskTracker OK!");
|
||||
#endif
|
||||
Service<GameNetwork>.Set();
|
||||
Service<GameGui>.Set();
|
||||
Service<GameNetwork>.Set();
|
||||
Service<GameGui>.Set();
|
||||
|
||||
framework.Enable();
|
||||
framework.Enable();
|
||||
|
||||
Log.Information("[T1] Load complete!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 1 load failed.");
|
||||
this.Unload();
|
||||
}
|
||||
Log.Information("[T1] Load complete!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs tier 2 of the Dalamud initialization process.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the load succeeded.</returns>
|
||||
public bool LoadTier2()
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
Log.Error(ex, "Tier 1 load failed.");
|
||||
this.Unload();
|
||||
}
|
||||
}
|
||||
|
||||
var antiDebug = Service<AntiDebug>.Set();
|
||||
if (!antiDebug.IsEnabled)
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs tier 2 of the Dalamud initialization process.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the load succeeded.</returns>
|
||||
public bool LoadTier2()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
var antiDebug = Service<AntiDebug>.Set();
|
||||
if (!antiDebug.IsEnabled)
|
||||
{
|
||||
#if DEBUG
|
||||
antiDebug.Enable();
|
||||
antiDebug.Enable();
|
||||
#else
|
||||
if (configuration.IsAntiAntiDebugEnabled)
|
||||
antiDebug.Enable();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Log.Information("[T2] AntiDebug OK!");
|
||||
Log.Information("[T2] AntiDebug OK!");
|
||||
|
||||
Service<WinSockHandlers>.Set();
|
||||
Log.Information("[T2] WinSock OK!");
|
||||
Service<WinSockHandlers>.Set();
|
||||
Log.Information("[T2] WinSock OK!");
|
||||
|
||||
Service<NetworkHandlers>.Set();
|
||||
Log.Information("[T2] NH OK!");
|
||||
Service<NetworkHandlers>.Set();
|
||||
Log.Information("[T2] NH OK!");
|
||||
|
||||
try
|
||||
{
|
||||
Service<DataManager>.Set().Initialize(this.AssetDirectory.FullName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not initialize DataManager.");
|
||||
this.Unload();
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
Service<DataManager>.Set().Initialize(this.AssetDirectory.FullName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not initialize DataManager.");
|
||||
this.Unload();
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Information("[T2] Data OK!");
|
||||
Log.Information("[T2] Data OK!");
|
||||
|
||||
var clientState = Service<ClientState>.Set();
|
||||
Log.Information("[T2] CS OK!");
|
||||
var clientState = Service<ClientState>.Set();
|
||||
Log.Information("[T2] CS OK!");
|
||||
|
||||
var localization = Service<Localization>.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_"));
|
||||
if (!string.IsNullOrEmpty(configuration.LanguageOverride))
|
||||
{
|
||||
localization.SetupWithLangCode(configuration.LanguageOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
localization.SetupWithUiCulture();
|
||||
}
|
||||
var localization = Service<Localization>.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_"));
|
||||
if (!string.IsNullOrEmpty(configuration.LanguageOverride))
|
||||
{
|
||||
localization.SetupWithLangCode(configuration.LanguageOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
localization.SetupWithUiCulture();
|
||||
}
|
||||
|
||||
Log.Information("[T2] LOC OK!");
|
||||
Log.Information("[T2] LOC OK!");
|
||||
|
||||
// This is enabled in ImGuiScene setup
|
||||
Service<DalamudIME>.Set();
|
||||
Log.Information("[T2] IME OK!");
|
||||
// This is enabled in ImGuiScene setup
|
||||
Service<DalamudIME>.Set();
|
||||
Log.Information("[T2] IME OK!");
|
||||
|
||||
Service<InterfaceManager>.Set().Enable();
|
||||
Log.Information("[T2] IM OK!");
|
||||
Service<InterfaceManager>.Set().Enable();
|
||||
Log.Information("[T2] IM OK!");
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Service<SeStringManager>.Set();
|
||||
Service<SeStringManager>.Set();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
Log.Information("[T2] SeString OK!");
|
||||
Log.Information("[T2] SeString OK!");
|
||||
|
||||
// Initialize managers. Basically handlers for the logic
|
||||
Service<CommandManager>.Set();
|
||||
// Initialize managers. Basically handlers for the logic
|
||||
Service<CommandManager>.Set();
|
||||
|
||||
Service<DalamudCommands>.Set().SetupCommands();
|
||||
Service<DalamudCommands>.Set().SetupCommands();
|
||||
|
||||
Log.Information("[T2] CM OK!");
|
||||
Log.Information("[T2] CM OK!");
|
||||
|
||||
Service<ChatHandlers>.Set();
|
||||
Service<ChatHandlers>.Set();
|
||||
|
||||
Log.Information("[T2] CH OK!");
|
||||
Log.Information("[T2] CH OK!");
|
||||
|
||||
clientState.Enable();
|
||||
Log.Information("[T2] CS ENABLE!");
|
||||
clientState.Enable();
|
||||
Log.Information("[T2] CS ENABLE!");
|
||||
|
||||
Service<DalamudAtkTweaks>.Set().Enable();
|
||||
Service<DalamudAtkTweaks>.Set().Enable();
|
||||
|
||||
Log.Information("[T2] Load complete!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 2 load failed.");
|
||||
this.Unload();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
Log.Information("[T2] Load complete!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 2 load failed.");
|
||||
this.Unload();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs tier 3 of the Dalamud initialization process.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the load succeeded.</returns>
|
||||
public bool LoadTier3()
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs tier 3 of the Dalamud initialization process.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the load succeeded.</returns>
|
||||
public bool LoadTier3()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information("[T3] START!");
|
||||
|
||||
var pluginManager = Service<PluginManager>.Set();
|
||||
Service<CallGate>.Set();
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information("[T3] START!");
|
||||
_ = pluginManager.SetPluginReposFromConfigAsync(false);
|
||||
|
||||
var pluginManager = Service<PluginManager>.Set();
|
||||
Service<CallGate>.Set();
|
||||
pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting;
|
||||
|
||||
try
|
||||
{
|
||||
_ = pluginManager.SetPluginReposFromConfigAsync(false);
|
||||
Log.Information("[T3] PM OK!");
|
||||
|
||||
pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting;
|
||||
pluginManager.CleanupPlugins();
|
||||
Log.Information("[T3] PMC OK!");
|
||||
|
||||
Log.Information("[T3] PM OK!");
|
||||
|
||||
pluginManager.CleanupPlugins();
|
||||
Log.Information("[T3] PMC OK!");
|
||||
|
||||
pluginManager.LoadAllPlugins();
|
||||
Log.Information("[T3] PML OK!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Plugin load failed.");
|
||||
}
|
||||
|
||||
Service<DalamudInterface>.Set();
|
||||
Log.Information("[T3] DUI OK!");
|
||||
|
||||
Troubleshooting.LogTroubleshooting();
|
||||
|
||||
Log.Information("Dalamud is ready.");
|
||||
pluginManager.LoadAllPlugins();
|
||||
Log.Information("[T3] PML OK!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 3 load failed.");
|
||||
this.Unload();
|
||||
|
||||
return false;
|
||||
Log.Error(ex, "Plugin load failed.");
|
||||
}
|
||||
|
||||
return true;
|
||||
Service<DalamudInterface>.Set();
|
||||
Log.Information("[T3] DUI OK!");
|
||||
|
||||
Troubleshooting.LogTroubleshooting();
|
||||
|
||||
Log.Information("Dalamud is ready.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 3 load failed.");
|
||||
this.Unload();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an unload of Dalamud when it gets the chance.
|
||||
/// </summary>
|
||||
public void Unload()
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an unload of Dalamud when it gets the chance.
|
||||
/// </summary>
|
||||
public void Unload()
|
||||
{
|
||||
Log.Information("Trigger unload");
|
||||
this.unloadSignal.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for an unload request to start.
|
||||
/// </summary>
|
||||
public void WaitForUnload()
|
||||
{
|
||||
this.unloadSignal.WaitOne();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for a queued unload to be finalized.
|
||||
/// </summary>
|
||||
public void WaitForUnloadFinish()
|
||||
{
|
||||
this.finishUnloadSignal?.WaitOne();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose subsystems related to plugin handling.
|
||||
/// </summary>
|
||||
public void DisposePlugins()
|
||||
{
|
||||
this.hasDisposedPlugins = true;
|
||||
|
||||
// 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<DalamudIME>.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<InterfaceManager>.GetNullable()?.Dispose();
|
||||
|
||||
Service<DalamudInterface>.GetNullable()?.Dispose();
|
||||
|
||||
Service<PluginManager>.GetNullable()?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose Dalamud subsystems.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information("Trigger unload");
|
||||
this.unloadSignal.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for an unload request to start.
|
||||
/// </summary>
|
||||
public void WaitForUnload()
|
||||
{
|
||||
this.unloadSignal.WaitOne();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for a queued unload to be finalized.
|
||||
/// </summary>
|
||||
public void WaitForUnloadFinish()
|
||||
{
|
||||
this.finishUnloadSignal?.WaitOne();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose subsystems related to plugin handling.
|
||||
/// </summary>
|
||||
public void DisposePlugins()
|
||||
{
|
||||
this.hasDisposedPlugins = true;
|
||||
|
||||
// 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<DalamudIME>.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<InterfaceManager>.GetNullable()?.Dispose();
|
||||
|
||||
Service<DalamudInterface>.GetNullable()?.Dispose();
|
||||
|
||||
Service<PluginManager>.GetNullable()?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose Dalamud subsystems.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
if (!this.hasDisposedPlugins)
|
||||
{
|
||||
if (!this.hasDisposedPlugins)
|
||||
{
|
||||
this.DisposePlugins();
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
Service<Framework>.GetNullable()?.Dispose();
|
||||
Service<ClientState>.GetNullable()?.Dispose();
|
||||
|
||||
this.unloadSignal?.Dispose();
|
||||
|
||||
Service<WinSockHandlers>.GetNullable()?.Dispose();
|
||||
Service<DataManager>.GetNullable()?.Dispose();
|
||||
Service<AntiDebug>.GetNullable()?.Dispose();
|
||||
Service<DalamudAtkTweaks>.GetNullable()?.Dispose();
|
||||
Service<HookManager>.GetNullable()?.Dispose();
|
||||
Service<SigScanner>.GetNullable()?.Dispose();
|
||||
|
||||
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
|
||||
|
||||
this.processMonoHook?.Dispose();
|
||||
|
||||
Log.Debug("Dalamud::Dispose() OK!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Dalamud::Dispose() failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the built-in exception handler with a debug one.
|
||||
/// </summary>
|
||||
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<SigScanner>.Get().ScanText(releaseSig);
|
||||
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
||||
|
||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
||||
Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
|
||||
}
|
||||
|
||||
private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e)
|
||||
{
|
||||
if (e.Exception == null)
|
||||
return;
|
||||
|
||||
Troubleshooting.LogException(e.Exception, e.Line);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch method for the class Process.Handle. This patch facilitates fixing Reloaded so that it
|
||||
/// uses pseudo-handles to access memory, to prevent permission errors.
|
||||
/// It should never be called manually.
|
||||
/// </summary>
|
||||
/// <param name="orig">A delegate that acts as the original method.</param>
|
||||
/// <param name="self">The equivalent of `this`.</param>
|
||||
/// <returns>A pseudo-handle for the current process, or the result from the original method.</returns>
|
||||
private static IntPtr ProcessHandlePatch(Func<Process, IntPtr> orig, Process self)
|
||||
{
|
||||
var result = orig(self);
|
||||
|
||||
if (self.Id == Environment.ProcessId)
|
||||
{
|
||||
result = (IntPtr)0xFFFFFFFF;
|
||||
this.DisposePlugins();
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}");
|
||||
return result;
|
||||
Service<Framework>.GetNullable()?.Dispose();
|
||||
Service<ClientState>.GetNullable()?.Dispose();
|
||||
|
||||
this.unloadSignal?.Dispose();
|
||||
|
||||
Service<WinSockHandlers>.GetNullable()?.Dispose();
|
||||
Service<DataManager>.GetNullable()?.Dispose();
|
||||
Service<AntiDebug>.GetNullable()?.Dispose();
|
||||
Service<DalamudAtkTweaks>.GetNullable()?.Dispose();
|
||||
Service<HookManager>.GetNullable()?.Dispose();
|
||||
Service<SigScanner>.GetNullable()?.Dispose();
|
||||
|
||||
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
|
||||
|
||||
this.processMonoHook?.Dispose();
|
||||
|
||||
Log.Debug("Dalamud::Dispose() OK!");
|
||||
}
|
||||
|
||||
private void ApplyProcessPatch()
|
||||
catch (Exception ex)
|
||||
{
|
||||
var targetType = typeof(Process);
|
||||
|
||||
var handleTarget = targetType.GetProperty(nameof(Process.Handle)).GetGetMethod();
|
||||
var handlePatch = typeof(Dalamud).GetMethod(nameof(Dalamud.ProcessHandlePatch), BindingFlags.NonPublic | BindingFlags.Static);
|
||||
this.processMonoHook = new MonoMod.RuntimeDetour.Hook(handleTarget, handlePatch);
|
||||
Log.Error(ex, "Dalamud::Dispose() failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the built-in exception handler with a debug one.
|
||||
/// </summary>
|
||||
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<SigScanner>.Get().ScanText(releaseSig);
|
||||
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
||||
|
||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
||||
Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
|
||||
}
|
||||
|
||||
private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e)
|
||||
{
|
||||
if (e.Exception == null)
|
||||
return;
|
||||
|
||||
Troubleshooting.LogException(e.Exception, e.Line);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch method for the class Process.Handle. This patch facilitates fixing Reloaded so that it
|
||||
/// uses pseudo-handles to access memory, to prevent permission errors.
|
||||
/// It should never be called manually.
|
||||
/// </summary>
|
||||
/// <param name="orig">A delegate that acts as the original method.</param>
|
||||
/// <param name="self">The equivalent of `this`.</param>
|
||||
/// <returns>A pseudo-handle for the current process, or the result from the original method.</returns>
|
||||
private static IntPtr ProcessHandlePatch(Func<Process, IntPtr> orig, Process self)
|
||||
{
|
||||
var result = orig(self);
|
||||
|
||||
if (self.Id == Environment.ProcessId)
|
||||
{
|
||||
result = (IntPtr)0xFFFFFFFF;
|
||||
}
|
||||
|
||||
// Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}");
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ApplyProcessPatch()
|
||||
{
|
||||
var targetType = typeof(Process);
|
||||
|
||||
var handleTarget = targetType.GetProperty(nameof(Process.Handle)).GetGetMethod();
|
||||
var handlePatch = typeof(Dalamud).GetMethod(nameof(Dalamud.ProcessHandlePatch), BindingFlags.NonPublic | BindingFlags.Static);
|
||||
this.processMonoHook = new MonoMod.RuntimeDetour.Hook(handleTarget, handlePatch);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,58 +3,57 @@ using System;
|
|||
using Dalamud.Game;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
/// Struct containing information needed to initialize Dalamud.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public record DalamudStartInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Struct containing information needed to initialize Dalamud.
|
||||
/// Gets or sets the working directory of the XIVLauncher installations.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public record DalamudStartInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the working directory of the XIVLauncher installations.
|
||||
/// </summary>
|
||||
public string WorkingDirectory { get; set; }
|
||||
public string WorkingDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the configuration file.
|
||||
/// </summary>
|
||||
public string ConfigurationPath { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the path to the configuration file.
|
||||
/// </summary>
|
||||
public string ConfigurationPath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the directory for installed plugins.
|
||||
/// </summary>
|
||||
public string PluginDirectory { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the path to the directory for installed plugins.
|
||||
/// </summary>
|
||||
public string PluginDirectory { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the directory for developer plugins.
|
||||
/// </summary>
|
||||
public string DefaultPluginDirectory { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the path to the directory for developer plugins.
|
||||
/// </summary>
|
||||
public string DefaultPluginDirectory { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to core Dalamud assets.
|
||||
/// </summary>
|
||||
public string AssetDirectory { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the path to core Dalamud assets.
|
||||
/// </summary>
|
||||
public string AssetDirectory { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language of the game client.
|
||||
/// </summary>
|
||||
public ClientLanguage Language { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the language of the game client.
|
||||
/// </summary>
|
||||
public ClientLanguage Language { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current game version code.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(GameVersionConverter))]
|
||||
public GameVersion GameVersion { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the current game version code.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(GameVersionConverter))]
|
||||
public GameVersion GameVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not market board information should be uploaded by default.
|
||||
/// </summary>
|
||||
public bool OptOutMbCollection { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not market board information should be uploaded by default.
|
||||
/// </summary>
|
||||
public bool OptOutMbCollection { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that specifies how much to wait before a new Dalamud session.
|
||||
/// </summary>
|
||||
public int DelayInitializeMs { get; init; } = 0;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a value that specifies how much to wait before a new Dalamud session.
|
||||
/// </summary>
|
||||
public int DelayInitializeMs { get; init; } = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,330 +18,329 @@ using Lumina.Excel;
|
|||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Data
|
||||
namespace Dalamud.Data;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class DataManager : IDisposable
|
||||
{
|
||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||
|
||||
private Thread luminaResourceThread;
|
||||
private CancellationTokenSource luminaCancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
|
||||
/// Initializes a new instance of the <see cref="DataManager"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class DataManager : IDisposable
|
||||
internal DataManager()
|
||||
{
|
||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||
this.Language = Service<DalamudStartInfo>.Get().Language;
|
||||
|
||||
private Thread luminaResourceThread;
|
||||
private CancellationTokenSource luminaCancellationTokenSource;
|
||||
// Set up default values so plugins do not null-reference when data is being loaded.
|
||||
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataManager"/> class.
|
||||
/// </summary>
|
||||
internal DataManager()
|
||||
/// <summary>
|
||||
/// Gets the current game client language.
|
||||
/// </summary>
|
||||
public ClientLanguage Language { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpCodes sent by the server to the client.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpCodes sent by the client to the server.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Lumina"/> object which gives access to any excel/game data.
|
||||
/// </summary>
|
||||
public GameData GameData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
|
||||
/// </summary>
|
||||
public ExcelModule Excel => this.GameData?.Excel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether Game Data is ready to be read.
|
||||
/// </summary>
|
||||
public bool IsDataReady { get; private set; }
|
||||
|
||||
#region Lumina Wrappers
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
||||
{
|
||||
return this.Excel.GetSheet<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type with a specified language.
|
||||
/// </summary>
|
||||
/// <param name="language">Language of the sheet to get.</param>
|
||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
||||
{
|
||||
return this.Excel.GetSheet<T>(language.ToLumina());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="FileResource"/> with the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||
public FileResource? GetFile(string path)
|
||||
{
|
||||
return this.GetFile<FileResource>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of resource.</typeparam>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||
public T? GetFile<T>(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<T>(filePath.Category, filePath) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the file with the given path exists within the game's index files.
|
||||
/// </summary>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>True if the file exists.</returns>
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return this.GameData.FileExists(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(uint iconId)
|
||||
{
|
||||
return this.GetIcon(this.Language, iconId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given quality.
|
||||
/// </summary>
|
||||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(bool isHq, uint iconId)
|
||||
{
|
||||
var type = isHq ? "hq/" : string.Empty;
|
||||
return this.GetIcon(type, iconId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given language.
|
||||
/// </summary>
|
||||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
|
||||
{
|
||||
var type = iconLanguage switch
|
||||
{
|
||||
this.Language = Service<DalamudStartInfo>.Get().Language;
|
||||
ClientLanguage.Japanese => "ja/",
|
||||
ClientLanguage.English => "en/",
|
||||
ClientLanguage.German => "de/",
|
||||
ClientLanguage.French => "fr/",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
|
||||
};
|
||||
|
||||
// Set up default values so plugins do not null-reference when data is being loaded.
|
||||
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
|
||||
}
|
||||
return this.GetIcon(type, iconId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current game client language.
|
||||
/// </summary>
|
||||
public ClientLanguage Language { get; private set; }
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(string type, uint iconId)
|
||||
{
|
||||
type ??= string.Empty;
|
||||
if (type.Length > 0 && !type.EndsWith("/"))
|
||||
type += "/";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpCodes sent by the server to the client.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
||||
var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId);
|
||||
var file = this.GetFile<TexFile>(filePath);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpCodes sent by the client to the server.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Lumina"/> object which gives access to any excel/game data.
|
||||
/// </summary>
|
||||
public GameData GameData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
|
||||
/// </summary>
|
||||
public ExcelModule Excel => this.GameData?.Excel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether Game Data is ready to be read.
|
||||
/// </summary>
|
||||
public bool IsDataReady { get; private set; }
|
||||
|
||||
#region Lumina Wrappers
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
||||
{
|
||||
return this.Excel.GetSheet<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type with a specified language.
|
||||
/// </summary>
|
||||
/// <param name="language">Language of the sheet to get.</param>
|
||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
||||
{
|
||||
return this.Excel.GetSheet<T>(language.ToLumina());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="FileResource"/> with the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||
public FileResource? GetFile(string path)
|
||||
{
|
||||
return this.GetFile<FileResource>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of resource.</typeparam>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||
public T? GetFile<T>(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<T>(filePath.Category, filePath) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the file with the given path exists within the game's index files.
|
||||
/// </summary>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>True if the file exists.</returns>
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return this.GameData.FileExists(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(uint iconId)
|
||||
{
|
||||
return this.GetIcon(this.Language, iconId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given quality.
|
||||
/// </summary>
|
||||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(bool isHq, uint iconId)
|
||||
{
|
||||
var type = isHq ? "hq/" : string.Empty;
|
||||
return this.GetIcon(type, iconId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given language.
|
||||
/// </summary>
|
||||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
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<TexFile>(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<TexFile>(filePath);
|
||||
if (type == string.Empty || file != default)
|
||||
return file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the HQ icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetHqIcon(uint iconId)
|
||||
=> this.GetIcon(true, iconId);
|
||||
// Couldn't get specific type, try for generic version.
|
||||
filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
|
||||
file = this.GetFile<TexFile>(filePath);
|
||||
return file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the passed <see cref="TexFile"/> as a drawable ImGui TextureWrap.
|
||||
/// </summary>
|
||||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
public TextureWrap? GetImGuiTexture(TexFile? tex)
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the HQ icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetHqIcon(uint iconId)
|
||||
=> this.GetIcon(true, iconId);
|
||||
|
||||
/// <summary>
|
||||
/// Get the passed <see cref="TexFile"/> as a drawable ImGui TextureWrap.
|
||||
/// </summary>
|
||||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
public TextureWrap? GetImGuiTexture(TexFile? tex)
|
||||
{
|
||||
return tex == null ? null : Service<InterfaceManager>.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
||||
/// </summary>
|
||||
/// <param name="path">The internal path to the texture.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
public TextureWrap? GetImGuiTexture(string path)
|
||||
=> this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconId));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given quality.
|
||||
/// </summary>
|
||||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(isHq, iconId));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given language.
|
||||
/// </summary>
|
||||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(string type, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the HQ icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetHqIcon(iconId));
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this DataManager.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.luminaCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize this data manager.
|
||||
/// </summary>
|
||||
/// <param name="baseDir">The directory to load data from.</param>
|
||||
internal void Initialize(string baseDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
return tex == null ? null : Service<InterfaceManager>.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||
}
|
||||
Log.Verbose("Starting data load...");
|
||||
|
||||
/// <summary>
|
||||
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
||||
/// </summary>
|
||||
/// <param name="path">The internal path to the texture.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
public TextureWrap? GetImGuiTexture(string path)
|
||||
=> this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
||||
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
|
||||
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconId));
|
||||
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given quality.
|
||||
/// </summary>
|
||||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(isHq, iconId));
|
||||
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
|
||||
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given language.
|
||||
/// </summary>
|
||||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
||||
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(string type, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the HQ icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetHqIcon(iconId));
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this DataManager.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.luminaCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize this data manager.
|
||||
/// </summary>
|
||||
/// <param name="baseDir">The directory to load data from.</param>
|
||||
internal void Initialize(string baseDir)
|
||||
{
|
||||
try
|
||||
var luminaOptions = new LuminaOptions
|
||||
{
|
||||
Log.Verbose("Starting data load...");
|
||||
|
||||
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
|
||||
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
|
||||
|
||||
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
|
||||
|
||||
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
|
||||
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
|
||||
|
||||
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
|
||||
|
||||
var luminaOptions = new LuminaOptions
|
||||
{
|
||||
CacheFileResources = true,
|
||||
CacheFileResources = true,
|
||||
#if DEBUG
|
||||
PanicOnSheetChecksumMismatch = true,
|
||||
PanicOnSheetChecksumMismatch = true,
|
||||
#else
|
||||
PanicOnSheetChecksumMismatch = false,
|
||||
#endif
|
||||
DefaultExcelLanguage = this.Language.ToLumina(),
|
||||
};
|
||||
DefaultExcelLanguage = this.Language.ToLumina(),
|
||||
};
|
||||
|
||||
var processModule = Process.GetCurrentProcess().MainModule;
|
||||
if (processModule != null)
|
||||
{
|
||||
this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions);
|
||||
}
|
||||
|
||||
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
||||
|
||||
this.IsDataReady = true;
|
||||
|
||||
this.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)
|
||||
var processModule = Process.GetCurrentProcess().MainModule;
|
||||
if (processModule != null)
|
||||
{
|
||||
Log.Error(ex, "Could not download data.");
|
||||
this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions);
|
||||
}
|
||||
|
||||
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
||||
|
||||
this.IsDataReady = true;
|
||||
|
||||
this.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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,262 +21,261 @@ using Serilog.Events;
|
|||
|
||||
using static Dalamud.NativeFunctions;
|
||||
|
||||
namespace Dalamud
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
/// The main entrypoint for the Dalamud system.
|
||||
/// </summary>
|
||||
public sealed class EntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entrypoint for the Dalamud system.
|
||||
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
||||
/// </summary>
|
||||
public sealed class EntryPoint
|
||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||
public delegate void InitDelegate(IntPtr infoPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize Dalamud.
|
||||
/// </summary>
|
||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||
public static void Initialize(IntPtr infoPtr)
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
||||
/// </summary>
|
||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||
public delegate void InitDelegate(IntPtr infoPtr);
|
||||
var infoStr = Marshal.PtrToStringUTF8(infoPtr);
|
||||
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize Dalamud.
|
||||
/// </summary>
|
||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||
public static void Initialize(IntPtr infoPtr)
|
||||
new Thread(() => RunThread(info)).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all Dalamud subsystems and start running on the main thread.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="DalamudStartInfo"/> containing information needed to initialize Dalamud.</param>
|
||||
private static void RunThread(DalamudStartInfo info)
|
||||
{
|
||||
if (EnvironmentConfiguration.DalamudWaitForDebugger)
|
||||
{
|
||||
var infoStr = Marshal.PtrToStringUTF8(infoPtr);
|
||||
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr);
|
||||
|
||||
new Thread(() => RunThread(info)).Start();
|
||||
while (!Debugger.IsAttached)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all Dalamud subsystems and start running on the main thread.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="DalamudStartInfo"/> containing information needed to initialize Dalamud.</param>
|
||||
private static void RunThread(DalamudStartInfo info)
|
||||
{
|
||||
if (EnvironmentConfiguration.DalamudWaitForDebugger)
|
||||
{
|
||||
while (!Debugger.IsAttached)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
// Setup logger
|
||||
var levelSwitch = InitLogging(info.WorkingDirectory);
|
||||
|
||||
// Setup logger
|
||||
var levelSwitch = InitLogging(info.WorkingDirectory);
|
||||
// Load configuration first to get some early persistent state, like log level
|
||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath);
|
||||
|
||||
// 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
|
||||
// Set the appropriate logging level from the configuration
|
||||
#if !DEBUG
|
||||
levelSwitch.MinimumLevel = configuration.LogLevel;
|
||||
#endif
|
||||
|
||||
// Log any unhandled exception.
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||
// Log any unhandled exception.
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||
|
||||
var finishSignal = new ManualResetEvent(false);
|
||||
var finishSignal = new ManualResetEvent(false);
|
||||
|
||||
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, levelSwitch, finishSignal, configuration);
|
||||
Log.Information("Starting a session..");
|
||||
|
||||
// Run session
|
||||
dalamud.LoadTier1();
|
||||
dalamud.WaitForUnload();
|
||||
|
||||
dalamud.Dispose();
|
||||
}
|
||||
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();
|
||||
|
||||
finishSignal.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitSymbolHandler(DalamudStartInfo info)
|
||||
try
|
||||
{
|
||||
try
|
||||
if (info.DelayInitializeMs > 0)
|
||||
{
|
||||
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.");
|
||||
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, levelSwitch, finishSignal, configuration);
|
||||
Log.Information("Starting a session..");
|
||||
|
||||
// Run session
|
||||
dalamud.LoadTier1();
|
||||
dalamud.WaitForUnload();
|
||||
|
||||
dalamud.Dispose();
|
||||
}
|
||||
|
||||
private static LoggingLevelSwitch InitLogging(string baseDirectory)
|
||||
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();
|
||||
|
||||
finishSignal.Set();
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
private static LoggingLevelSwitch InitLogging(string baseDirectory)
|
||||
{
|
||||
#if DEBUG
|
||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||
var oldPath = Path.Combine(baseDirectory, "dalamud.log.old");
|
||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||
var oldPath = Path.Combine(baseDirectory, "dalamud.log.old");
|
||||
#else
|
||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
|
||||
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
|
||||
#endif
|
||||
|
||||
CullLogFile(logPath, oldPath, 1 * 1024 * 1024);
|
||||
CullLogFile(oldPath, null, 10 * 1024 * 1024);
|
||||
CullLogFile(logPath, oldPath, 1 * 1024 * 1024);
|
||||
CullLogFile(oldPath, null, 10 * 1024 * 1024);
|
||||
|
||||
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
|
||||
return levelSwitch;
|
||||
}
|
||||
return levelSwitch;
|
||||
}
|
||||
|
||||
private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize)
|
||||
private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
var bufferSize = 4096;
|
||||
|
||||
var logFile = new FileInfo(logPath);
|
||||
|
||||
if (!logFile.Exists)
|
||||
logFile.Create();
|
||||
|
||||
if (logFile.Length <= cullingFileSize)
|
||||
return;
|
||||
|
||||
var amountToCull = logFile.Length - cullingFileSize;
|
||||
|
||||
if (amountToCull < bufferSize)
|
||||
return;
|
||||
|
||||
if (oldPath != null)
|
||||
{
|
||||
var bufferSize = 4096;
|
||||
var oldFile = new FileInfo(oldPath);
|
||||
|
||||
var logFile = new FileInfo(logPath);
|
||||
if (!oldFile.Exists)
|
||||
oldFile.Create().Close();
|
||||
|
||||
if (!logFile.Exists)
|
||||
logFile.Create();
|
||||
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
using var writer = new BinaryWriter(oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite));
|
||||
|
||||
if (logFile.Length <= cullingFileSize)
|
||||
return;
|
||||
|
||||
var amountToCull = logFile.Length - cullingFileSize;
|
||||
|
||||
if (amountToCull < bufferSize)
|
||||
return;
|
||||
|
||||
if (oldPath != null)
|
||||
var read = -1;
|
||||
var total = 0;
|
||||
var buffer = new byte[bufferSize];
|
||||
while (read != 0 && total < amountToCull)
|
||||
{
|
||||
var oldFile = new FileInfo(oldPath);
|
||||
|
||||
if (!oldFile.Exists)
|
||||
oldFile.Create().Close();
|
||||
|
||||
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
using var writer = new BinaryWriter(oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite));
|
||||
|
||||
var read = -1;
|
||||
var total = 0;
|
||||
var buffer = new byte[bufferSize];
|
||||
while (read != 0 && total < amountToCull)
|
||||
{
|
||||
read = reader.Read(buffer, 0, buffer.Length);
|
||||
writer.Write(buffer, 0, read);
|
||||
total += read;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite));
|
||||
|
||||
reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin);
|
||||
|
||||
var read = -1;
|
||||
var total = 0;
|
||||
var buffer = new byte[bufferSize];
|
||||
while (read != 0)
|
||||
{
|
||||
read = reader.Read(buffer, 0, buffer.Length);
|
||||
writer.Write(buffer, 0, read);
|
||||
total += read;
|
||||
}
|
||||
|
||||
writer.BaseStream.SetLength(total);
|
||||
read = reader.Read(buffer, 0, buffer.Length);
|
||||
writer.Write(buffer, 0, read);
|
||||
total += read;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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);
|
||||
*/
|
||||
{
|
||||
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite));
|
||||
|
||||
reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin);
|
||||
|
||||
var read = -1;
|
||||
var total = 0;
|
||||
var buffer = new byte[bufferSize];
|
||||
while (read != 0)
|
||||
{
|
||||
read = reader.Read(buffer, 0, buffer.Length);
|
||||
writer.Write(buffer, 0, read);
|
||||
total += read;
|
||||
}
|
||||
|
||||
writer.BaseStream.SetLength(total);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||
catch (Exception ex)
|
||||
{
|
||||
switch (args.ExceptionObject)
|
||||
{
|
||||
case Exception ex:
|
||||
Log.Fatal(ex, "Unhandled exception on AppDomain");
|
||||
Troubleshooting.LogException(ex, "DalamudUnhandled");
|
||||
Log.Error(ex, "Log cull failed");
|
||||
|
||||
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<DalamudConfiguration>.Get();
|
||||
config.PluginSafeMode = true;
|
||||
config.Save();
|
||||
}
|
||||
|
||||
Environment.Exit(-1);
|
||||
|
||||
break;
|
||||
default:
|
||||
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
|
||||
{
|
||||
if (!args.Observed)
|
||||
Log.Error(args.Exception, "Unobserved exception in Task.");
|
||||
/*
|
||||
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<DalamudConfiguration>.Get();
|
||||
config.PluginSafeMode = true;
|
||||
config.Save();
|
||||
}
|
||||
|
||||
Environment.Exit(-1);
|
||||
|
||||
break;
|
||||
default:
|
||||
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
|
||||
{
|
||||
if (!args.Observed)
|
||||
Log.Error(args.Exception, "Unobserved exception in Task.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,112 +3,111 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Base memory address resolver.
|
||||
/// </summary>
|
||||
public abstract class BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Base memory address resolver.
|
||||
/// Gets a list of memory addresses that were found, to list in /xldata.
|
||||
/// </summary>
|
||||
public abstract class BaseAddressResolver
|
||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
||||
/// </summary>
|
||||
protected bool IsResolved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||
/// </summary>
|
||||
public void Setup()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of memory addresses that were found, to list in /xldata.
|
||||
/// </summary>
|
||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
||||
var scanner = Service<SigScanner>.Get();
|
||||
this.Setup(scanner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
||||
/// </summary>
|
||||
protected bool IsResolved { get; set; }
|
||||
/// <summary>
|
||||
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
public void Setup(SigScanner scanner)
|
||||
{
|
||||
// Because C# don't allow to call virtual function while in ctor
|
||||
// we have to do this shit :\
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||
/// </summary>
|
||||
public void Setup()
|
||||
if (this.IsResolved)
|
||||
{
|
||||
var scanner = Service<SigScanner>.Get();
|
||||
this.Setup(scanner);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
public void Setup(SigScanner scanner)
|
||||
if (scanner.Is32BitProcess)
|
||||
{
|
||||
// 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;
|
||||
DebugScannedValues[className] = new List<(string, IntPtr)>();
|
||||
|
||||
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
||||
{
|
||||
DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this)));
|
||||
}
|
||||
|
||||
this.IsResolved = true;
|
||||
this.Setup32Bit(scanner);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Setup64Bit(scanner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The delegate to marshal the function pointer to.</typeparam>
|
||||
/// <param name="address">The address of the virtual table.</param>
|
||||
/// <param name="vtableOffset">The offset from address to the vtable pointer.</param>
|
||||
/// <param name="count">The vfunc index.</param>
|
||||
/// <returns>A delegate function pointer that can be invoked.</returns>
|
||||
public T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class
|
||||
this.SetupInternal(scanner);
|
||||
|
||||
var className = this.GetType().Name;
|
||||
DebugScannedValues[className] = new List<(string, IntPtr)>();
|
||||
|
||||
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
||||
{
|
||||
// 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<T>(functionAddress);
|
||||
DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void Setup32Bit(SigScanner scanner)
|
||||
{
|
||||
throw new NotSupportedException("32 bit version is not supported.");
|
||||
}
|
||||
this.IsResolved = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
throw new NotSupportedException("64 bit version is not supported.");
|
||||
}
|
||||
/// <summary>
|
||||
/// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The delegate to marshal the function pointer to.</typeparam>
|
||||
/// <param name="address">The address of the virtual table.</param>
|
||||
/// <param name="vtableOffset">The offset from address to the vtable pointer.</param>
|
||||
/// <param name="count">The vfunc index.</param>
|
||||
/// <returns>A delegate function pointer that can be invoked.</returns>
|
||||
public T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class
|
||||
{
|
||||
// Get vtable
|
||||
var vtable = Marshal.ReadIntPtr(address, vtableOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void SetupInternal(SigScanner scanner)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
// Get an address to the function
|
||||
var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void Setup32Bit(SigScanner scanner)
|
||||
{
|
||||
throw new NotSupportedException("32 bit version is not supported.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
throw new NotSupportedException("64 bit version is not supported.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void SetupInternal(SigScanner scanner)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,298 +21,298 @@ using Dalamud.Plugin.Internal;
|
|||
using Dalamud.Utility;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Chat events and public helper functions.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public class ChatHandlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Chat events and public helper functions.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public class ChatHandlers
|
||||
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||
// {
|
||||
// { "", "<:ffxive071:585847382210642069>" },
|
||||
// { "", "<:ffxive083:585848592699490329>" },
|
||||
// };
|
||||
|
||||
// private readonly Dictionary<XivChatType, Color> 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|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
||||
{
|
||||
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||
// {
|
||||
// { "", "<:ffxive071:585847382210642069>" },
|
||||
// { "", "<:ffxive083:585848592699490329>" },
|
||||
// };
|
||||
|
||||
// private readonly Dictionary<XivChatType, Color> 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|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
||||
{
|
||||
ClientLanguage.Japanese,
|
||||
new Regex[]
|
||||
{
|
||||
ClientLanguage.Japanese,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.English,
|
||||
new Regex[]
|
||||
{
|
||||
ClientLanguage.English,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.German,
|
||||
new Regex[]
|
||||
{
|
||||
ClientLanguage.German,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) für (?<value>[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
|
||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.French,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||
|
||||
private readonly DalamudLinkPayload openInstallerWindowLink;
|
||||
|
||||
private bool hasSeenLoadingMsg;
|
||||
private bool hasAutoUpdatedPlugins;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
|
||||
/// </summary>
|
||||
internal ChatHandlers()
|
||||
{
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
|
||||
chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||
chatGui.ChatMessage += this.OnChatMessage;
|
||||
|
||||
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||
{
|
||||
Service<DalamudInterface>.Get().OpenPluginInstaller();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last URL seen in chat.
|
||||
/// </summary>
|
||||
public string? LastLink { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to convert.</param>
|
||||
/// <returns>SeString payload of italicized text.</returns>
|
||||
public static SeString MakeItalics(string text)
|
||||
=> MakeItalics(new TextPayload(text));
|
||||
|
||||
/// <summary>
|
||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to convert.</param>
|
||||
/// <returns>SeString payload of italicized text.</returns>
|
||||
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 configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
var textVal = message.TextValue;
|
||||
|
||||
if (!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;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.BadWords != null &&
|
||||
configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||
},
|
||||
{
|
||||
ClientLanguage.French,
|
||||
new Regex[]
|
||||
{
|
||||
// This seems to be in the user block list - let's not show it
|
||||
Log.Debug("Blocklist triggered");
|
||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||
|
||||
private readonly DalamudLinkPayload openInstallerWindowLink;
|
||||
|
||||
private bool hasSeenLoadingMsg;
|
||||
private bool hasAutoUpdatedPlugins;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
|
||||
/// </summary>
|
||||
internal ChatHandlers()
|
||||
{
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
|
||||
chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||
chatGui.ChatMessage += this.OnChatMessage;
|
||||
|
||||
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||
{
|
||||
Service<DalamudInterface>.Get().OpenPluginInstaller();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last URL seen in chat.
|
||||
/// </summary>
|
||||
public string? LastLink { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to convert.</param>
|
||||
/// <returns>SeString payload of italicized text.</returns>
|
||||
public static SeString MakeItalics(string text)
|
||||
=> MakeItalics(new TextPayload(text));
|
||||
|
||||
/// <summary>
|
||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to convert.</param>
|
||||
/// <returns>SeString payload of italicized text.</returns>
|
||||
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 configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
var textVal = message.TextValue;
|
||||
|
||||
if (!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 (configuration.BadWords != null &&
|
||||
configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
var clientState = Service<ClientState.ClientState>.Get();
|
||||
// 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<DalamudStartInfo>.Get();
|
||||
var clientState = Service<ClientState.ClientState>.Get();
|
||||
|
||||
// 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<ChatGui>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
var dalamudInterface = Service<DalamudInterface>.Get();
|
||||
|
||||
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));
|
||||
|
||||
if (configuration.PrintPluginsWelcomeMsg)
|
||||
{
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
var dalamudInterface = Service<DalamudInterface>.Get();
|
||||
|
||||
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));
|
||||
|
||||
if (configuration.PrintPluginsWelcomeMsg)
|
||||
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name))
|
||||
{
|
||||
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name))
|
||||
{
|
||||
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(configuration.LastVersion) || !assemblyVersion.StartsWith(configuration.LastVersion))
|
||||
{
|
||||
chatGui.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."),
|
||||
Type = XivChatType.Notice,
|
||||
});
|
||||
|
||||
if (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
|
||||
{
|
||||
dalamudInterface.OpenChangelogWindow();
|
||||
configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
|
||||
}
|
||||
|
||||
configuration.LastVersion = assemblyVersion;
|
||||
configuration.Save();
|
||||
}
|
||||
|
||||
this.hasSeenLoadingMsg = true;
|
||||
}
|
||||
|
||||
private void AutoUpdatePlugins()
|
||||
if (string.IsNullOrEmpty(configuration.LastVersion) || !assemblyVersion.StartsWith(configuration.LastVersion))
|
||||
{
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
var notifications = Service<NotificationManager>.Get();
|
||||
|
||||
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
|
||||
chatGui.PrintChat(new XivChatEntry
|
||||
{
|
||||
// Plugins aren't ready yet.
|
||||
Message = Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."),
|
||||
Type = XivChatType.Notice,
|
||||
});
|
||||
|
||||
if (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
|
||||
{
|
||||
dalamudInterface.OpenChangelogWindow();
|
||||
configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
|
||||
}
|
||||
|
||||
configuration.LastVersion = assemblyVersion;
|
||||
configuration.Save();
|
||||
}
|
||||
|
||||
this.hasSeenLoadingMsg = true;
|
||||
}
|
||||
|
||||
private void AutoUpdatePlugins()
|
||||
{
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
var notifications = Service<NotificationManager>.Get();
|
||||
|
||||
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
|
||||
{
|
||||
// Plugins aren't ready yet.
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasAutoUpdatedPlugins = true;
|
||||
|
||||
Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task =>
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasAutoUpdatedPlugins = true;
|
||||
|
||||
Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task =>
|
||||
var updatedPlugins = task.Result;
|
||||
if (updatedPlugins != null && updatedPlugins.Any())
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
if (configuration.AutoUpdatePlugins)
|
||||
{
|
||||
Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
||||
return;
|
||||
pluginManager.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);
|
||||
}
|
||||
|
||||
var updatedPlugins = task.Result;
|
||||
if (updatedPlugins != null && updatedPlugins.Any())
|
||||
else
|
||||
{
|
||||
if (configuration.AutoUpdatePlugins)
|
||||
{
|
||||
pluginManager.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
|
||||
{
|
||||
var data = Service<DataManager>.Get();
|
||||
var data = Service<DataManager>.Get();
|
||||
|
||||
chatGui.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = new SeString(new List<Payload>()
|
||||
{
|
||||
chatGui.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = new SeString(new List<Payload>()
|
||||
{
|
||||
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),
|
||||
|
|
@ -321,12 +321,11 @@ namespace Dalamud.Game
|
|||
RawPayload.LinkTerminator,
|
||||
new UIForegroundPayload(0),
|
||||
new TextPayload("]"),
|
||||
}),
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
}),
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,180 +7,179 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the buddies present in your squadron or trust party.
|
||||
/// It does not include the local player.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class BuddyList
|
||||
{
|
||||
private const uint InvalidObjectID = 0xE0000000;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the buddies present in your squadron or trust party.
|
||||
/// It does not include the local player.
|
||||
/// Initializes a new instance of the <see cref="BuddyList"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class BuddyList
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal BuddyList(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
private const uint InvalidObjectID = 0xE0000000;
|
||||
this.address = addressResolver;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuddyList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal BuddyList(ClientStateAddressResolver addressResolver)
|
||||
/// <summary>
|
||||
/// Gets the amount of battle buddies the local player has.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
this.address = addressResolver;
|
||||
|
||||
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of battle buddies the local player has.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player's companion is present.
|
||||
/// </summary>
|
||||
public bool CompanionBuddyPresent => this.CompanionBuddy != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player's pet is present.
|
||||
/// </summary>
|
||||
public bool PetBuddyPresent => this.PetBuddy != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active companion buddy.
|
||||
/// </summary>
|
||||
public BuddyMember? CompanionBuddy
|
||||
{
|
||||
get
|
||||
{
|
||||
var addr = this.GetCompanionBuddyMemberAddress();
|
||||
return this.CreateBuddyMemberReference(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active pet buddy.
|
||||
/// </summary>
|
||||
public BuddyMember? PetBuddy
|
||||
{
|
||||
get
|
||||
{
|
||||
var addr = this.GetPetBuddyMemberAddress();
|
||||
return this.CreateBuddyMemberReference(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
internal IntPtr BuddyListAddress => this.address.BuddyList;
|
||||
|
||||
private static int BuddyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy>();
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a battle buddy at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns>
|
||||
public BuddyMember? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var address = this.GetBattleBuddyMemberAddress(index);
|
||||
return this.CreateBuddyMemberReference(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the companion buddy.
|
||||
/// </summary>
|
||||
/// <returns>The memory address of the companion buddy.</returns>
|
||||
public unsafe IntPtr GetCompanionBuddyMemberAddress()
|
||||
{
|
||||
return (IntPtr)(&this.BuddyListStruct->Companion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the pet buddy.
|
||||
/// </summary>
|
||||
/// <returns>The memory address of the pet buddy.</returns>
|
||||
public unsafe IntPtr GetPetBuddyMemberAddress()
|
||||
{
|
||||
return (IntPtr)(&this.BuddyListStruct->Pet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the battle buddy at the specified index of the buddy list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the battle buddy.</param>
|
||||
/// <returns>The memory address of the battle buddy.</returns>
|
||||
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= 3)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to a buddy.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the buddy in memory.</param>
|
||||
/// <returns><see cref="BuddyMember"/> object containing the requested data.</returns>
|
||||
public BuddyMember? CreateBuddyMemberReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the buddies present in your squadron or trust party.
|
||||
/// Gets a value indicating whether the local player's companion is present.
|
||||
/// </summary>
|
||||
public sealed partial class BuddyList : IReadOnlyCollection<BuddyMember>
|
||||
public bool CompanionBuddyPresent => this.CompanionBuddy != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player's pet is present.
|
||||
/// </summary>
|
||||
public bool PetBuddyPresent => this.PetBuddy != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active companion buddy.
|
||||
/// </summary>
|
||||
public BuddyMember? CompanionBuddy
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<BuddyMember> GetEnumerator()
|
||||
get
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
var addr = this.GetCompanionBuddyMemberAddress();
|
||||
return this.CreateBuddyMemberReference(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
/// <summary>
|
||||
/// Gets the active pet buddy.
|
||||
/// </summary>
|
||||
public BuddyMember? PetBuddy
|
||||
{
|
||||
get
|
||||
{
|
||||
var addr = this.GetPetBuddyMemberAddress();
|
||||
return this.CreateBuddyMemberReference(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
internal IntPtr BuddyListAddress => this.address.BuddyList;
|
||||
|
||||
private static int BuddyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy>();
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a battle buddy at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns>
|
||||
public BuddyMember? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var address = this.GetBattleBuddyMemberAddress(index);
|
||||
return this.CreateBuddyMemberReference(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the companion buddy.
|
||||
/// </summary>
|
||||
/// <returns>The memory address of the companion buddy.</returns>
|
||||
public unsafe IntPtr GetCompanionBuddyMemberAddress()
|
||||
{
|
||||
return (IntPtr)(&this.BuddyListStruct->Companion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the pet buddy.
|
||||
/// </summary>
|
||||
/// <returns>The memory address of the pet buddy.</returns>
|
||||
public unsafe IntPtr GetPetBuddyMemberAddress()
|
||||
{
|
||||
return (IntPtr)(&this.BuddyListStruct->Pet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the battle buddy at the specified index of the buddy list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the battle buddy.</param>
|
||||
/// <returns>The memory address of the battle buddy.</returns>
|
||||
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= 3)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to a buddy.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the buddy in memory.</param>
|
||||
/// <returns><see cref="BuddyMember"/> object containing the requested data.</returns>
|
||||
public BuddyMember? CreateBuddyMemberReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
var buddy = new BuddyMember(address);
|
||||
if (buddy.ObjectId == InvalidObjectID)
|
||||
return null;
|
||||
|
||||
return buddy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the buddies present in your squadron or trust party.
|
||||
/// </summary>
|
||||
public sealed partial class BuddyList : IReadOnlyCollection<BuddyMember>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<BuddyMember> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,70 +4,69 @@ 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;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// </summary>
|
||||
public unsafe class BuddyMember
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
||||
/// </summary>
|
||||
public unsafe class BuddyMember
|
||||
/// <param name="address">Buddy address.</param>
|
||||
internal BuddyMember(IntPtr address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Buddy address.</param>
|
||||
internal BuddyMember(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of this buddy.
|
||||
/// </summary>
|
||||
public uint ObjectId => this.Struct->ObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor associated with this buddy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
public GameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.ObjectId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current health of this buddy.
|
||||
/// </summary>
|
||||
public uint CurrentHP => this.Struct->CurrentHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum health of this buddy.
|
||||
/// </summary>
|
||||
public uint MaxHP => this.Struct->MaxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data ID of this buddy.
|
||||
/// </summary>
|
||||
public uint DataID => this.Struct->DataID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Mount data related to this buddy. It should only be used with companion buddies.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData => new(this.DataID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Pet data related to this buddy. It should only be used with pet buddies.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData => new(this.DataID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Trust data related to this buddy. It should only be used with battle buddies.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of this buddy.
|
||||
/// </summary>
|
||||
public uint ObjectId => this.Struct->ObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor associated with this buddy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
public GameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.ObjectId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current health of this buddy.
|
||||
/// </summary>
|
||||
public uint CurrentHP => this.Struct->CurrentHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum health of this buddy.
|
||||
/// </summary>
|
||||
public uint MaxHP => this.Struct->MaxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data ID of this buddy.
|
||||
/// </summary>
|
||||
public uint DataID => this.Struct->DataID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Mount data related to this buddy. It should only be used with companion buddies.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData => new(this.DataID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Pet data related to this buddy. It should only be used with pet buddies.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData => new(this.DataID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Trust data related to this buddy. It should only be used with battle buddies.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData => new(this.DataID);
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,165 +16,164 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
namespace Dalamud.Game.ClientState;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the state of the game client at the time of access.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class ClientState : IDisposable
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||
|
||||
private bool lastConditionNone = true;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the state of the game client at the time of access.
|
||||
/// Initializes a new instance of the <see cref="ClientState"/> class.
|
||||
/// Set up client state access.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class ClientState : IDisposable
|
||||
internal ClientState()
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||
this.address = new ClientStateAddressResolver();
|
||||
this.address.Setup();
|
||||
|
||||
private bool lastConditionNone = true;
|
||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClientState"/> class.
|
||||
/// Set up client state access.
|
||||
/// </summary>
|
||||
internal ClientState()
|
||||
this.ClientLanguage = Service<DalamudStartInfo>.Get().Language;
|
||||
|
||||
Service<ObjectTable>.Set(this.address);
|
||||
|
||||
Service<FateTable>.Set(this.address);
|
||||
|
||||
Service<PartyList>.Set(this.address);
|
||||
|
||||
Service<BuddyList>.Set(this.address);
|
||||
|
||||
Service<JobGauges>.Set(this.address);
|
||||
|
||||
Service<KeyState>.Set(this.address);
|
||||
|
||||
Service<GamepadState>.Set(this.address);
|
||||
|
||||
Service<Condition>.Set(this.address);
|
||||
|
||||
Service<TargetManager>.Set(this.address);
|
||||
|
||||
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
||||
|
||||
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
||||
|
||||
var framework = Service<Framework>.Get();
|
||||
framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||
|
||||
var networkHandlers = Service<NetworkHandlers>.Get();
|
||||
networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when the current Territory changes.
|
||||
/// </summary>
|
||||
public event EventHandler<ushort> TerritoryChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a character is logging in.
|
||||
/// </summary>
|
||||
public event EventHandler Login;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a character is logging out.
|
||||
/// </summary>
|
||||
public event EventHandler Logout;
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when a duty is ready.
|
||||
/// </summary>
|
||||
public event EventHandler<Lumina.Excel.GeneratedSheets.ContentFinderCondition> CfPop;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language of the client.
|
||||
/// </summary>
|
||||
public ClientLanguage ClientLanguage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Territory the player resides in.
|
||||
/// </summary>
|
||||
public ushort TerritoryType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local player character, if one is present.
|
||||
/// </summary>
|
||||
public PlayerCharacter? LocalPlayer => Service<ObjectTable>.Get()[0] as PlayerCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content ID of the local character.
|
||||
/// </summary>
|
||||
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a character is logged in.
|
||||
/// </summary>
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
Service<Condition>.Get().Enable();
|
||||
Service<GamepadState>.Get().Enable();
|
||||
this.setupTerritoryTypeHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.setupTerritoryTypeHook.Dispose();
|
||||
Service<Condition>.Get().Dispose();
|
||||
Service<GamepadState>.Get().Dispose();
|
||||
Service<Framework>.Get().Update -= this.FrameworkOnOnUpdateEvent;
|
||||
Service<NetworkHandlers>.Get().CfPop -= this.NetworkHandlersOnCfPop;
|
||||
}
|
||||
|
||||
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
||||
{
|
||||
this.TerritoryType = terriType;
|
||||
this.TerritoryChanged?.Invoke(this, terriType);
|
||||
|
||||
Log.Debug("TerritoryType changed: {0}", terriType);
|
||||
|
||||
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
||||
}
|
||||
|
||||
private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e)
|
||||
{
|
||||
this.CfPop?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void FrameworkOnOnUpdateEvent(Framework framework)
|
||||
{
|
||||
var condition = Service<Condition>.Get();
|
||||
if (condition.Any() && this.lastConditionNone == true)
|
||||
{
|
||||
this.address = new ClientStateAddressResolver();
|
||||
this.address.Setup();
|
||||
|
||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
||||
|
||||
this.ClientLanguage = Service<DalamudStartInfo>.Get().Language;
|
||||
|
||||
Service<ObjectTable>.Set(this.address);
|
||||
|
||||
Service<FateTable>.Set(this.address);
|
||||
|
||||
Service<PartyList>.Set(this.address);
|
||||
|
||||
Service<BuddyList>.Set(this.address);
|
||||
|
||||
Service<JobGauges>.Set(this.address);
|
||||
|
||||
Service<KeyState>.Set(this.address);
|
||||
|
||||
Service<GamepadState>.Set(this.address);
|
||||
|
||||
Service<Condition>.Set(this.address);
|
||||
|
||||
Service<TargetManager>.Set(this.address);
|
||||
|
||||
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
||||
|
||||
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
||||
|
||||
var framework = Service<Framework>.Get();
|
||||
framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||
|
||||
var networkHandlers = Service<NetworkHandlers>.Get();
|
||||
networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||
Log.Debug("Is login");
|
||||
this.lastConditionNone = false;
|
||||
this.IsLoggedIn = true;
|
||||
this.Login?.Invoke(this, null);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when the current Territory changes.
|
||||
/// </summary>
|
||||
public event EventHandler<ushort> TerritoryChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a character is logging in.
|
||||
/// </summary>
|
||||
public event EventHandler Login;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a character is logging out.
|
||||
/// </summary>
|
||||
public event EventHandler Logout;
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when a duty is ready.
|
||||
/// </summary>
|
||||
public event EventHandler<Lumina.Excel.GeneratedSheets.ContentFinderCondition> CfPop;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language of the client.
|
||||
/// </summary>
|
||||
public ClientLanguage ClientLanguage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Territory the player resides in.
|
||||
/// </summary>
|
||||
public ushort TerritoryType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local player character, if one is present.
|
||||
/// </summary>
|
||||
public PlayerCharacter? LocalPlayer => Service<ObjectTable>.Get()[0] as PlayerCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content ID of the local character.
|
||||
/// </summary>
|
||||
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a character is logged in.
|
||||
/// </summary>
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
if (!condition.Any() && this.lastConditionNone == false)
|
||||
{
|
||||
Service<Condition>.Get().Enable();
|
||||
Service<GamepadState>.Get().Enable();
|
||||
this.setupTerritoryTypeHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.setupTerritoryTypeHook.Dispose();
|
||||
Service<Condition>.Get().Dispose();
|
||||
Service<GamepadState>.Get().Dispose();
|
||||
Service<Framework>.Get().Update -= this.FrameworkOnOnUpdateEvent;
|
||||
Service<NetworkHandlers>.Get().CfPop -= this.NetworkHandlersOnCfPop;
|
||||
}
|
||||
|
||||
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
||||
{
|
||||
this.TerritoryType = terriType;
|
||||
this.TerritoryChanged?.Invoke(this, terriType);
|
||||
|
||||
Log.Debug("TerritoryType changed: {0}", terriType);
|
||||
|
||||
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
||||
}
|
||||
|
||||
private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e)
|
||||
{
|
||||
this.CfPop?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void FrameworkOnOnUpdateEvent(Framework framework)
|
||||
{
|
||||
var condition = Service<Condition>.Get();
|
||||
if (condition.Any() && this.lastConditionNone == true)
|
||||
{
|
||||
Log.Debug("Is login");
|
||||
this.lastConditionNone = false;
|
||||
this.IsLoggedIn = true;
|
||||
this.Login?.Invoke(this, null);
|
||||
}
|
||||
|
||||
if (!condition.Any() && this.lastConditionNone == false)
|
||||
{
|
||||
Log.Debug("Is logout");
|
||||
this.lastConditionNone = true;
|
||||
this.IsLoggedIn = false;
|
||||
this.Logout?.Invoke(this, null);
|
||||
}
|
||||
Log.Debug("Is logout");
|
||||
this.lastConditionNone = true;
|
||||
this.IsLoggedIn = false;
|
||||
this.Logout?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,114 +1,113 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
namespace Dalamud.Game.ClientState;
|
||||
|
||||
/// <summary>
|
||||
/// Client state memory address resolver.
|
||||
/// </summary>
|
||||
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||
{
|
||||
// Static offsets
|
||||
|
||||
/// <summary>
|
||||
/// Client state memory address resolver.
|
||||
/// Gets the address of the actor table.
|
||||
/// </summary>
|
||||
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||
public IntPtr ObjectTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
public IntPtr BuddyList { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of a pointer to the fate table.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a static address to a pointer, not the address of the table itself.
|
||||
/// </remarks>
|
||||
public IntPtr FateTablePtr { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Group Manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the local content id.
|
||||
/// </summary>
|
||||
public IntPtr LocalContentId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of job gauge data.
|
||||
/// </summary>
|
||||
public IntPtr JobGaugeData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the keyboard state.
|
||||
/// </summary>
|
||||
public IntPtr KeyboardState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the keyboard state index array which translates the VK enumeration to the key state.
|
||||
/// </summary>
|
||||
public IntPtr KeyboardStateIndexArray { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the target manager.
|
||||
/// </summary>
|
||||
public IntPtr TargetManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the condition flag array.
|
||||
/// </summary>
|
||||
public IntPtr ConditionFlags { get; private set; }
|
||||
|
||||
// Functions
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method which sets the territory type.
|
||||
/// </summary>
|
||||
public IntPtr SetupTerritoryType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method which polls the gamepads for data.
|
||||
/// Called every frame, even when `Enable Gamepad` is off in the settings.
|
||||
/// </summary>
|
||||
public IntPtr GamepadPoll { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
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 ?? ?? ?? ??");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the actor table.
|
||||
/// </summary>
|
||||
public IntPtr ObjectTable { get; private set; }
|
||||
this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
public IntPtr BuddyList { get; private set; }
|
||||
this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of a pointer to the fate table.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a static address to a pointer, not the address of the table itself.
|
||||
/// </remarks>
|
||||
public IntPtr FateTablePtr { get; private set; }
|
||||
this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Group Manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupManager { get; private set; }
|
||||
this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the local content id.
|
||||
/// </summary>
|
||||
public IntPtr LocalContentId { get; private set; }
|
||||
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
||||
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of job gauge data.
|
||||
/// </summary>
|
||||
public IntPtr JobGaugeData { get; private set; }
|
||||
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the keyboard state.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the keyboard state index array which translates the VK enumeration to the key state.
|
||||
/// </summary>
|
||||
public IntPtr KeyboardStateIndexArray { get; private set; }
|
||||
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the target manager.
|
||||
/// </summary>
|
||||
public IntPtr TargetManager { get; private set; }
|
||||
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the condition flag array.
|
||||
/// </summary>
|
||||
public IntPtr ConditionFlags { get; private set; }
|
||||
|
||||
// Functions
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method which sets the territory type.
|
||||
/// </summary>
|
||||
public IntPtr SetupTerritoryType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method which polls the gamepads for data.
|
||||
/// Called every frame, even when `Enable Gamepad` is off in the settings.
|
||||
/// </summary>
|
||||
public IntPtr GamepadPoll { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
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 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8;
|
||||
|
||||
this.SetupTerritoryType = sig.ScanText("48 89 5C 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.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,155 +4,154 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Conditions
|
||||
namespace Dalamud.Game.ClientState.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class Condition
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class Condition
|
||||
public const int MaxConditionEntries = 100;
|
||||
|
||||
private readonly bool[] cache = new bool[MaxConditionEntries];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Condition"/> class.
|
||||
/// </summary>
|
||||
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
|
||||
internal Condition(ClientStateAddressResolver resolver)
|
||||
{
|
||||
/// <summary>
|
||||
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||
/// </summary>
|
||||
public const int MaxConditionEntries = 100;
|
||||
this.Address = resolver.ConditionFlags;
|
||||
}
|
||||
|
||||
private readonly bool[] cache = new bool[MaxConditionEntries];
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ConditionChange"/> event.
|
||||
/// </summary>
|
||||
/// <param name="flag">The changed condition.</param>
|
||||
/// <param name="value">The value the condition is set to.</param>
|
||||
public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Condition"/> class.
|
||||
/// </summary>
|
||||
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
|
||||
internal Condition(ClientStateAddressResolver resolver)
|
||||
/// <summary>
|
||||
/// Event that gets fired when a condition is set.
|
||||
/// Should only get fired for actual changes, so the previous value will always be !value.
|
||||
/// </summary>
|
||||
public event ConditionChangeDelegate? ConditionChange;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the condition array base pointer.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Check the value of a specific condition/state flag.
|
||||
/// </summary>
|
||||
/// <param name="flag">The condition flag to check.</param>
|
||||
public unsafe bool this[int flag]
|
||||
{
|
||||
get
|
||||
{
|
||||
this.Address = resolver.ConditionFlags;
|
||||
if (flag < 0 || flag >= MaxConditionEntries)
|
||||
return false;
|
||||
|
||||
return *(bool*)(this.Address + flag);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="this[int]"/>
|
||||
public unsafe bool this[ConditionFlag flag]
|
||||
=> this[(int)flag];
|
||||
|
||||
/// <summary>
|
||||
/// Check if any condition flags are set.
|
||||
/// </summary>
|
||||
/// <returns>Whether any single flag is set.</returns>
|
||||
public bool Any()
|
||||
{
|
||||
for (var i = 0; i < MaxConditionEntries; i++)
|
||||
{
|
||||
var cond = this[i];
|
||||
|
||||
if (cond)
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ConditionChange"/> event.
|
||||
/// </summary>
|
||||
/// <param name="flag">The changed condition.</param>
|
||||
/// <param name="value">The value the condition is set to.</param>
|
||||
public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when a condition is set.
|
||||
/// Should only get fired for actual changes, so the previous value will always be !value.
|
||||
/// </summary>
|
||||
public event ConditionChangeDelegate? ConditionChange;
|
||||
/// <summary>
|
||||
/// Enables the hooks of the Condition class function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
// Initialization
|
||||
for (var i = 0; i < MaxConditionEntries; i++)
|
||||
this.cache[i] = this[i];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the condition array base pointer.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; private set; }
|
||||
Service<Framework>.Get().Update += this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the value of a specific condition/state flag.
|
||||
/// </summary>
|
||||
/// <param name="flag">The condition flag to check.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="this[int]"/>
|
||||
public unsafe bool this[ConditionFlag flag]
|
||||
=> this[(int)flag];
|
||||
|
||||
/// <summary>
|
||||
/// Check if any condition flags are set.
|
||||
/// </summary>
|
||||
/// <returns>Whether any single flag is set.</returns>
|
||||
public bool Any()
|
||||
{
|
||||
for (var i = 0; i < MaxConditionEntries; i++)
|
||||
{
|
||||
var cond = this[i];
|
||||
|
||||
if (cond)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the hooks of the Condition class function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
// Initialization
|
||||
for (var i = 0; i < MaxConditionEntries; i++)
|
||||
this.cache[i] = this[i];
|
||||
|
||||
Service<Framework>.Get().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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||
/// </summary>
|
||||
public sealed partial class Condition : IDisposable
|
||||
{
|
||||
private bool isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||
/// Finalizes an instance of the <see cref="Condition" /> class.
|
||||
/// </summary>
|
||||
public sealed partial class Condition : IDisposable
|
||||
~Condition()
|
||||
{
|
||||
private bool isDisposed;
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="Condition" /> class.
|
||||
/// </summary>
|
||||
~Condition()
|
||||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
this.Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.isDisposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
this.Dispose(false);
|
||||
Service<Framework>.Get().Update -= this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
this.Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.isDisposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Service<Framework>.Get().Update -= this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
this.isDisposed = true;
|
||||
}
|
||||
this.isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,453 +1,452 @@
|
|||
namespace Dalamud.Game.ClientState.Conditions
|
||||
namespace Dalamud.Game.ClientState.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public enum ConditionFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public enum ConditionFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// Unused.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command under normal conditions.
|
||||
/// </summary>
|
||||
NormalConditions = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while unconscious.
|
||||
/// </summary>
|
||||
Unconscious = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command during an emote.
|
||||
/// </summary>
|
||||
Emoting = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounted.
|
||||
/// </summary>
|
||||
Mounted = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while crafting.
|
||||
/// </summary>
|
||||
Crafting = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while gathering.
|
||||
/// </summary>
|
||||
Gathering = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while melding materia.
|
||||
/// </summary>
|
||||
MeldingMateria = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while operating a siege machine.
|
||||
/// </summary>
|
||||
OperatingSiegeMachine = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while carrying an object.
|
||||
/// </summary>
|
||||
CarryingObject = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounted.
|
||||
/// </summary>
|
||||
Mounted2 = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while in that position.
|
||||
/// </summary>
|
||||
InThatPosition = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while chocobo racing.
|
||||
/// </summary>
|
||||
ChocoboRacing = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while playing a mini-game.
|
||||
/// </summary>
|
||||
PlayingMiniGame = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while playing Lord of Verminion.
|
||||
/// </summary>
|
||||
PlayingLordOfVerminion = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while participating in a custom match.
|
||||
/// </summary>
|
||||
ParticipatingInCustomMatch = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while performing.
|
||||
/// </summary>
|
||||
Performing = 16,
|
||||
|
||||
// Unknown17 = 17,
|
||||
// Unknown18 = 18,
|
||||
// Unknown19 = 19,
|
||||
// Unknown20 = 20,
|
||||
// Unknown21 = 21,
|
||||
// Unknown22 = 22,
|
||||
// Unknown23 = 23,
|
||||
// Unknown24 = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied = 25,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command during combat.
|
||||
/// </summary>
|
||||
InCombat = 26,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while casting.
|
||||
/// </summary>
|
||||
Casting = 27,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction2 = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied30 = 30,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
// todo: not sure if this is used for other event states/???
|
||||
OccupiedInEvent = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
OccupiedInQuestEvent = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied33 = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundByDuty = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
OccupiedInCutSceneEvent = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while in a dueling area.
|
||||
/// </summary>
|
||||
InDuelingArea = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while a trade is open.
|
||||
/// </summary>
|
||||
TradeOpen = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied38 = 38,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied39 = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while crafting.
|
||||
/// </summary>
|
||||
Crafting40 = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while preparing to craft.
|
||||
/// </summary>
|
||||
PreparingToCraft = 41,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while gathering.
|
||||
/// </summary>
|
||||
Gathering42 = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while fishing.
|
||||
/// </summary>
|
||||
Fishing = 43,
|
||||
|
||||
// Unknown44 = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while between areas.
|
||||
/// </summary>
|
||||
BetweenAreas = 45,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while stealthed.
|
||||
/// </summary>
|
||||
Stealthed = 46,
|
||||
|
||||
// Unknown47 = 47,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while jumping.
|
||||
/// </summary>
|
||||
Jumping = 48,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while auto-run is active.
|
||||
/// </summary>
|
||||
AutorunActive = 49,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
// todo: used for other shits?
|
||||
OccupiedSummoningBell = 50,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while between areas.
|
||||
/// </summary>
|
||||
BetweenAreas51 = 51,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command due to system error.
|
||||
/// </summary>
|
||||
SystemError = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while logging out.
|
||||
/// </summary>
|
||||
LoggingOut = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command at this location.
|
||||
/// </summary>
|
||||
ConditionLocation = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for duty.
|
||||
/// </summary>
|
||||
WaitingForDuty = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundByDuty56 = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command at this time.
|
||||
/// </summary>
|
||||
Unknown57 = 57,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while watching a cutscene.
|
||||
/// </summary>
|
||||
WatchingCutscene = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for Duty Finder.
|
||||
/// </summary>
|
||||
WaitingForDutyFinder = 59,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while creating a character.
|
||||
/// </summary>
|
||||
CreatingCharacter = 60,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while jumping.
|
||||
/// </summary>
|
||||
Jumping61 = 61,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while the PvP display is active.
|
||||
/// </summary>
|
||||
PvPDisplayActive = 62,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction63 = 63,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounting.
|
||||
/// </summary>
|
||||
Mounting = 64,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while carrying an item.
|
||||
/// </summary>
|
||||
CarryingItem = 65,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while using the Party Finder.
|
||||
/// </summary>
|
||||
UsingPartyFinder = 66,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while using housing functions.
|
||||
/// </summary>
|
||||
UsingHousingFunctions = 67,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while transformed.
|
||||
/// </summary>
|
||||
Transformed = 68,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while on the free trial.
|
||||
/// </summary>
|
||||
OnFreeTrial = 69,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while being moved.
|
||||
/// </summary>
|
||||
BeingMoved = 70,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounting.
|
||||
/// </summary>
|
||||
Mounting71 = 71,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction72 = 72,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction73 = 73,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while registering for a race or match.
|
||||
/// </summary>
|
||||
RegisteringForRaceOrMatch = 74,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for a race or match.
|
||||
/// </summary>
|
||||
WaitingForRaceOrMatch = 75,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for a Triple Triad match.
|
||||
/// </summary>
|
||||
WaitingForTripleTriadMatch = 76,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while in flight.
|
||||
/// </summary>
|
||||
InFlight = 77,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while watching a cutscene.
|
||||
/// </summary>
|
||||
WatchingCutscene78 = 78,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while delving into a deep dungeon.
|
||||
/// </summary>
|
||||
InDeepDungeon = 79,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while swimming.
|
||||
/// </summary>
|
||||
Swimming = 80,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while diving.
|
||||
/// </summary>
|
||||
Diving = 81,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while registering for a Triple Triad match.
|
||||
/// </summary>
|
||||
RegisteringForTripleTriadMatch = 82,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for a Triple Triad match.
|
||||
/// </summary>
|
||||
WaitingForTripleTriadMatch83 = 83,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while participating in a cross-world party or alliance.
|
||||
/// </summary>
|
||||
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
||||
|
||||
// Unknown85 = 85,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while playing duty record.
|
||||
/// </summary>
|
||||
DutyRecorderPlayback = 86,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while casting.
|
||||
/// </summary>
|
||||
Casting87 = 87,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command in this state.
|
||||
/// </summary>
|
||||
InThisState88 = 88,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command in this state.
|
||||
/// </summary>
|
||||
InThisState89 = 89,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while role-playing.
|
||||
/// </summary>
|
||||
RolePlaying = 90,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundToDuty97 = 91,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while readying to visit another World.
|
||||
/// </summary>
|
||||
ReadyingVisitOtherWorld = 92,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting to visit another World.
|
||||
/// </summary>
|
||||
WaitingToVisitOtherWorld = 93,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while using a parasol.
|
||||
/// </summary>
|
||||
UsingParasol = 94,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundByDuty95 = 95,
|
||||
}
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command under normal conditions.
|
||||
/// </summary>
|
||||
NormalConditions = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while unconscious.
|
||||
/// </summary>
|
||||
Unconscious = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command during an emote.
|
||||
/// </summary>
|
||||
Emoting = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounted.
|
||||
/// </summary>
|
||||
Mounted = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while crafting.
|
||||
/// </summary>
|
||||
Crafting = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while gathering.
|
||||
/// </summary>
|
||||
Gathering = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while melding materia.
|
||||
/// </summary>
|
||||
MeldingMateria = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while operating a siege machine.
|
||||
/// </summary>
|
||||
OperatingSiegeMachine = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while carrying an object.
|
||||
/// </summary>
|
||||
CarryingObject = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounted.
|
||||
/// </summary>
|
||||
Mounted2 = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while in that position.
|
||||
/// </summary>
|
||||
InThatPosition = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while chocobo racing.
|
||||
/// </summary>
|
||||
ChocoboRacing = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while playing a mini-game.
|
||||
/// </summary>
|
||||
PlayingMiniGame = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while playing Lord of Verminion.
|
||||
/// </summary>
|
||||
PlayingLordOfVerminion = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while participating in a custom match.
|
||||
/// </summary>
|
||||
ParticipatingInCustomMatch = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while performing.
|
||||
/// </summary>
|
||||
Performing = 16,
|
||||
|
||||
// Unknown17 = 17,
|
||||
// Unknown18 = 18,
|
||||
// Unknown19 = 19,
|
||||
// Unknown20 = 20,
|
||||
// Unknown21 = 21,
|
||||
// Unknown22 = 22,
|
||||
// Unknown23 = 23,
|
||||
// Unknown24 = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied = 25,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command during combat.
|
||||
/// </summary>
|
||||
InCombat = 26,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while casting.
|
||||
/// </summary>
|
||||
Casting = 27,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction2 = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied30 = 30,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
// todo: not sure if this is used for other event states/???
|
||||
OccupiedInEvent = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
OccupiedInQuestEvent = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied33 = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundByDuty = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
OccupiedInCutSceneEvent = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while in a dueling area.
|
||||
/// </summary>
|
||||
InDuelingArea = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while a trade is open.
|
||||
/// </summary>
|
||||
TradeOpen = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied38 = 38,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
Occupied39 = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while crafting.
|
||||
/// </summary>
|
||||
Crafting40 = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while preparing to craft.
|
||||
/// </summary>
|
||||
PreparingToCraft = 41,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while gathering.
|
||||
/// </summary>
|
||||
Gathering42 = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while fishing.
|
||||
/// </summary>
|
||||
Fishing = 43,
|
||||
|
||||
// Unknown44 = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while between areas.
|
||||
/// </summary>
|
||||
BetweenAreas = 45,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while stealthed.
|
||||
/// </summary>
|
||||
Stealthed = 46,
|
||||
|
||||
// Unknown47 = 47,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while jumping.
|
||||
/// </summary>
|
||||
Jumping = 48,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while auto-run is active.
|
||||
/// </summary>
|
||||
AutorunActive = 49,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
/// </summary>
|
||||
// todo: used for other shits?
|
||||
OccupiedSummoningBell = 50,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while between areas.
|
||||
/// </summary>
|
||||
BetweenAreas51 = 51,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command due to system error.
|
||||
/// </summary>
|
||||
SystemError = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while logging out.
|
||||
/// </summary>
|
||||
LoggingOut = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command at this location.
|
||||
/// </summary>
|
||||
ConditionLocation = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for duty.
|
||||
/// </summary>
|
||||
WaitingForDuty = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundByDuty56 = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command at this time.
|
||||
/// </summary>
|
||||
Unknown57 = 57,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while watching a cutscene.
|
||||
/// </summary>
|
||||
WatchingCutscene = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for Duty Finder.
|
||||
/// </summary>
|
||||
WaitingForDutyFinder = 59,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while creating a character.
|
||||
/// </summary>
|
||||
CreatingCharacter = 60,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while jumping.
|
||||
/// </summary>
|
||||
Jumping61 = 61,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while the PvP display is active.
|
||||
/// </summary>
|
||||
PvPDisplayActive = 62,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction63 = 63,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounting.
|
||||
/// </summary>
|
||||
Mounting = 64,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while carrying an item.
|
||||
/// </summary>
|
||||
CarryingItem = 65,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while using the Party Finder.
|
||||
/// </summary>
|
||||
UsingPartyFinder = 66,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while using housing functions.
|
||||
/// </summary>
|
||||
UsingHousingFunctions = 67,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while transformed.
|
||||
/// </summary>
|
||||
Transformed = 68,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while on the free trial.
|
||||
/// </summary>
|
||||
OnFreeTrial = 69,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while being moved.
|
||||
/// </summary>
|
||||
BeingMoved = 70,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounting.
|
||||
/// </summary>
|
||||
Mounting71 = 71,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction72 = 72,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while suffering status affliction.
|
||||
/// </summary>
|
||||
SufferingStatusAffliction73 = 73,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while registering for a race or match.
|
||||
/// </summary>
|
||||
RegisteringForRaceOrMatch = 74,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for a race or match.
|
||||
/// </summary>
|
||||
WaitingForRaceOrMatch = 75,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for a Triple Triad match.
|
||||
/// </summary>
|
||||
WaitingForTripleTriadMatch = 76,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while in flight.
|
||||
/// </summary>
|
||||
InFlight = 77,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while watching a cutscene.
|
||||
/// </summary>
|
||||
WatchingCutscene78 = 78,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while delving into a deep dungeon.
|
||||
/// </summary>
|
||||
InDeepDungeon = 79,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while swimming.
|
||||
/// </summary>
|
||||
Swimming = 80,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while diving.
|
||||
/// </summary>
|
||||
Diving = 81,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while registering for a Triple Triad match.
|
||||
/// </summary>
|
||||
RegisteringForTripleTriadMatch = 82,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting for a Triple Triad match.
|
||||
/// </summary>
|
||||
WaitingForTripleTriadMatch83 = 83,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while participating in a cross-world party or alliance.
|
||||
/// </summary>
|
||||
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
||||
|
||||
// Unknown85 = 85,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while playing duty record.
|
||||
/// </summary>
|
||||
DutyRecorderPlayback = 86,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while casting.
|
||||
/// </summary>
|
||||
Casting87 = 87,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command in this state.
|
||||
/// </summary>
|
||||
InThisState88 = 88,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command in this state.
|
||||
/// </summary>
|
||||
InThisState89 = 89,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while role-playing.
|
||||
/// </summary>
|
||||
RolePlaying = 90,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundToDuty97 = 91,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while readying to visit another World.
|
||||
/// </summary>
|
||||
ReadyingVisitOtherWorld = 92,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while waiting to visit another World.
|
||||
/// </summary>
|
||||
WaitingToVisitOtherWorld = 93,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while using a parasol.
|
||||
/// </summary>
|
||||
UsingParasol = 94,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while bound by duty.
|
||||
/// </summary>
|
||||
BoundByDuty95 = 95,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// </summary>
|
||||
public unsafe partial class Fate : IEquatable<Fate>
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// Initializes a new instance of the <see cref="Fate"/> class.
|
||||
/// </summary>
|
||||
public unsafe partial class Fate : IEquatable<Fate>
|
||||
/// <param name="address">The address of this fate in memory.</param>
|
||||
internal Fate(IntPtr address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Fate"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this fate in memory.</param>
|
||||
internal Fate(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this Fate in memory.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this Fate is still valid in memory.
|
||||
/// </summary>
|
||||
/// <param name="fate">The fate to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static bool IsValid(Fate fate)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (fate == null)
|
||||
return false;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <returns>True or false.</returns>
|
||||
public bool IsValid() => IsValid(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool IEquatable<Fate>.Equals(Fate other) => this.FateId == other?.FateId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj) => ((IEquatable<Fate>)this).Equals(obj as Fate);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => this.FateId.GetHashCode();
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// Gets the address of this Fate in memory.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Fate ID of this <see cref="Fate" />.
|
||||
/// </summary>
|
||||
public ushort FateId => this.Struct->FateId;
|
||||
if (fate1 is null || fate2 is null)
|
||||
return Equals(fate1, fate2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets game data linked to this Fate.
|
||||
/// </summary>
|
||||
public Lumina.Excel.GeneratedSheets.Fate GameData => Service<DataManager>.Get().GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time this <see cref="Fate"/> started.
|
||||
/// </summary>
|
||||
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
||||
|
||||
/// <summary>
|
||||
/// Gets how long this <see cref="Fate"/> will run.
|
||||
/// </summary>
|
||||
public short Duration => this.Struct->Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining time in seconds for this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayname of this <see cref="Fate" />.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of this <see cref="Fate"/> (Running, Ended, Failed, Preparation, WaitingForEnd).
|
||||
/// </summary>
|
||||
public FateState State => (FateState)this.Struct->State;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the progress amount of this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public byte Progress => this.Struct->Progress;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public Vector3 Position => this.Struct->Location;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType => new(this.Struct->TerritoryID);
|
||||
return fate1.Equals(fate2);
|
||||
}
|
||||
|
||||
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this Fate is still valid in memory.
|
||||
/// </summary>
|
||||
/// <param name="fate">The fate to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static bool IsValid(Fate fate)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (fate == null)
|
||||
return false;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <returns>True or false.</returns>
|
||||
public bool IsValid() => IsValid(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool IEquatable<Fate>.Equals(Fate other) => this.FateId == other?.FateId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj) => ((IEquatable<Fate>)this).Equals(obj as Fate);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => this.FateId.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// </summary>
|
||||
public unsafe partial class Fate
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Fate ID of this <see cref="Fate" />.
|
||||
/// </summary>
|
||||
public ushort FateId => this.Struct->FateId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets game data linked to this Fate.
|
||||
/// </summary>
|
||||
public Lumina.Excel.GeneratedSheets.Fate GameData => Service<DataManager>.Get().GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time this <see cref="Fate"/> started.
|
||||
/// </summary>
|
||||
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
||||
|
||||
/// <summary>
|
||||
/// Gets how long this <see cref="Fate"/> will run.
|
||||
/// </summary>
|
||||
public short Duration => this.Struct->Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining time in seconds for this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayname of this <see cref="Fate" />.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of this <see cref="Fate"/> (Running, Ended, Failed, Preparation, WaitingForEnd).
|
||||
/// </summary>
|
||||
public FateState State => (FateState)this.Struct->State;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the progress amount of this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public byte Progress => this.Struct->Progress;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of this <see cref="Fate"/>.
|
||||
/// </summary>
|
||||
public Vector3 Position => this.Struct->Location;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType => new(this.Struct->TerritoryID);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
namespace Dalamud.Game.ClientState.Fates
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
||||
/// <summary>
|
||||
/// This represents the state of a single Fate.
|
||||
/// </summary>
|
||||
public enum FateState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the state of a single Fate.
|
||||
/// The Fate is active.
|
||||
/// </summary>
|
||||
public enum FateState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The Fate is active.
|
||||
/// </summary>
|
||||
Running = 0x02,
|
||||
Running = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// The Fate has ended.
|
||||
/// </summary>
|
||||
Ended = 0x04,
|
||||
/// <summary>
|
||||
/// The Fate has ended.
|
||||
/// </summary>
|
||||
Ended = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// The player failed the Fate.
|
||||
/// </summary>
|
||||
Failed = 0x05,
|
||||
/// <summary>
|
||||
/// The player failed the Fate.
|
||||
/// </summary>
|
||||
Failed = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// The Fate is preparing to run.
|
||||
/// </summary>
|
||||
Preparation = 0x07,
|
||||
/// <summary>
|
||||
/// The Fate is preparing to run.
|
||||
/// </summary>
|
||||
Preparation = 0x07,
|
||||
|
||||
/// <summary>
|
||||
/// The Fate is preparing to end.
|
||||
/// </summary>
|
||||
WaitingForEnd = 0x08,
|
||||
}
|
||||
/// <summary>
|
||||
/// The Fate is preparing to end.
|
||||
/// </summary>
|
||||
WaitingForEnd = 0x08,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,143 +6,142 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently available Fate events.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class FateTable
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently available Fate events.
|
||||
/// Initializes a new instance of the <see cref="FateTable"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class FateTable
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal FateTable(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
this.address = addressResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FateTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal FateTable(ClientStateAddressResolver addressResolver)
|
||||
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Fate table.
|
||||
/// </summary>
|
||||
public IntPtr Address => this.address.FateTablePtr;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of currently active Fates.
|
||||
/// </summary>
|
||||
public unsafe int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
this.address = addressResolver;
|
||||
|
||||
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Fate table.
|
||||
/// </summary>
|
||||
public IntPtr Address => this.address.FateTablePtr;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of currently active Fates.
|
||||
/// </summary>
|
||||
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
|
||||
var check = Struct->Unk80.ToInt64();
|
||||
if (check == 0)
|
||||
return 0;
|
||||
|
||||
var start = Struct->FirstFatePtr.ToInt64();
|
||||
var end = Struct->LastFatePtr.ToInt64();
|
||||
if (start == 0 || end == 0)
|
||||
return 0;
|
||||
|
||||
return (int)((end - start) / 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Fate table.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Get an actor at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>A <see cref="Fate"/> at the specified spawn index.</returns>
|
||||
public Fate? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var address = this.GetFateAddress(index);
|
||||
return this.CreateFateReference(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Fate at the specified index of the fate table.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the Fate.</param>
|
||||
/// <returns>The memory address of the Fate.</returns>
|
||||
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;
|
||||
|
||||
var firstFate = this.Struct->FirstFatePtr;
|
||||
return *(IntPtr*)(firstFate + (8 * index));
|
||||
}
|
||||
// Sonar used this to check if the table was safe to read
|
||||
var check = Struct->Unk80.ToInt64();
|
||||
if (check == 0)
|
||||
return 0;
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to a FFXIV actor.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset of the actor in memory.</param>
|
||||
/// <returns><see cref="Fate"/> object containing requested data.</returns>
|
||||
public Fate? CreateFateReference(IntPtr offset)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var start = Struct->FirstFatePtr.ToInt64();
|
||||
var end = Struct->LastFatePtr.ToInt64();
|
||||
if (start == 0 || end == 0)
|
||||
return 0;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (offset == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new Fate(offset);
|
||||
return (int)((end - start) / 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently available Fate events.
|
||||
/// Gets the address of the Fate table.
|
||||
/// </summary>
|
||||
public sealed partial class FateTable : IReadOnlyCollection<Fate>
|
||||
internal unsafe IntPtr FateTableAddress
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<Fate>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<Fate> GetEnumerator()
|
||||
get
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
if (this.address.FateTablePtr == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Get an actor at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>A <see cref="Fate"/> at the specified spawn index.</returns>
|
||||
public Fate? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var address = this.GetFateAddress(index);
|
||||
return this.CreateFateReference(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Fate at the specified index of the fate table.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the Fate.</param>
|
||||
/// <returns>The memory address of the Fate.</returns>
|
||||
public unsafe IntPtr GetFateAddress(int index)
|
||||
{
|
||||
if (index >= this.Length)
|
||||
return IntPtr.Zero;
|
||||
|
||||
var fateTable = this.FateTableAddress;
|
||||
if (fateTable == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
var firstFate = this.Struct->FirstFatePtr;
|
||||
return *(IntPtr*)(firstFate + (8 * index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to a FFXIV actor.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset of the actor in memory.</param>
|
||||
/// <returns><see cref="Fate"/> object containing requested data.</returns>
|
||||
public Fate? CreateFateReference(IntPtr offset)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (offset == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new Fate(offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently available Fate events.
|
||||
/// </summary>
|
||||
public sealed partial class FateTable : IReadOnlyCollection<Fate>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<Fate>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<Fate> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,95 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.GamePad
|
||||
namespace Dalamud.Game.ClientState.GamePad;
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask of the Button ushort used by the game.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GamepadButtons : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Bitmask of the Button ushort used by the game.
|
||||
/// No buttons pressed.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GamepadButtons : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// No buttons pressed.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Digipad up.
|
||||
/// </summary>
|
||||
DpadUp = 0x0001,
|
||||
/// <summary>
|
||||
/// Digipad up.
|
||||
/// </summary>
|
||||
DpadUp = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Digipad down.
|
||||
/// </summary>
|
||||
DpadDown = 0x0002,
|
||||
/// <summary>
|
||||
/// Digipad down.
|
||||
/// </summary>
|
||||
DpadDown = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// Digipad left.
|
||||
/// </summary>
|
||||
DpadLeft = 0x0004,
|
||||
/// <summary>
|
||||
/// Digipad left.
|
||||
/// </summary>
|
||||
DpadLeft = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// Digipad right.
|
||||
/// </summary>
|
||||
DpadRight = 0x0008,
|
||||
/// <summary>
|
||||
/// Digipad right.
|
||||
/// </summary>
|
||||
DpadRight = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// North action button. Triangle on PS, Y on Xbox.
|
||||
/// </summary>
|
||||
North = 0x0010,
|
||||
/// <summary>
|
||||
/// North action button. Triangle on PS, Y on Xbox.
|
||||
/// </summary>
|
||||
North = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// South action button. Cross on PS, A on Xbox.
|
||||
/// </summary>
|
||||
South = 0x0020,
|
||||
/// <summary>
|
||||
/// South action button. Cross on PS, A on Xbox.
|
||||
/// </summary>
|
||||
South = 0x0020,
|
||||
|
||||
/// <summary>
|
||||
/// West action button. Square on PS, X on Xbos.
|
||||
/// </summary>
|
||||
West = 0x0040,
|
||||
/// <summary>
|
||||
/// West action button. Square on PS, X on Xbos.
|
||||
/// </summary>
|
||||
West = 0x0040,
|
||||
|
||||
/// <summary>
|
||||
/// East action button. Circle on PS, B on Xbox.
|
||||
/// </summary>
|
||||
East = 0x0080,
|
||||
/// <summary>
|
||||
/// East action button. Circle on PS, B on Xbox.
|
||||
/// </summary>
|
||||
East = 0x0080,
|
||||
|
||||
/// <summary>
|
||||
/// First button on left shoulder side.
|
||||
/// </summary>
|
||||
L1 = 0x0100,
|
||||
/// <summary>
|
||||
/// First button on left shoulder side.
|
||||
/// </summary>
|
||||
L1 = 0x0100,
|
||||
|
||||
/// <summary>
|
||||
/// Second button on left shoulder side. Analog input lost in this bitmask.
|
||||
/// </summary>
|
||||
L2 = 0x0200,
|
||||
/// <summary>
|
||||
/// Second button on left shoulder side. Analog input lost in this bitmask.
|
||||
/// </summary>
|
||||
L2 = 0x0200,
|
||||
|
||||
/// <summary>
|
||||
/// Press on left analogue stick.
|
||||
/// </summary>
|
||||
L3 = 0x0400,
|
||||
/// <summary>
|
||||
/// Press on left analogue stick.
|
||||
/// </summary>
|
||||
L3 = 0x0400,
|
||||
|
||||
/// <summary>
|
||||
/// First button on right shoulder.
|
||||
/// </summary>
|
||||
R1 = 0x0800,
|
||||
/// <summary>
|
||||
/// First button on right shoulder.
|
||||
/// </summary>
|
||||
R1 = 0x0800,
|
||||
|
||||
/// <summary>
|
||||
/// Second button on right shoulder. Analog input lost in this bitmask.
|
||||
/// </summary>
|
||||
R2 = 0x1000,
|
||||
/// <summary>
|
||||
/// Second button on right shoulder. Analog input lost in this bitmask.
|
||||
/// </summary>
|
||||
R2 = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// Press on right analogue stick.
|
||||
/// </summary>
|
||||
R3 = 0x2000,
|
||||
/// <summary>
|
||||
/// Press on right analogue stick.
|
||||
/// </summary>
|
||||
R3 = 0x2000,
|
||||
|
||||
/// <summary>
|
||||
/// Button on the right inner side of the controller. Options on PS, Start on Xbox.
|
||||
/// </summary>
|
||||
Start = 0x8000,
|
||||
/// <summary>
|
||||
/// Button on the right inner side of the controller. Options on PS, Start on Xbox.
|
||||
/// </summary>
|
||||
Start = 0x8000,
|
||||
|
||||
/// <summary>
|
||||
/// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
|
||||
/// </summary>
|
||||
Select = 0x4000,
|
||||
}
|
||||
/// <summary>
|
||||
/// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
|
||||
/// </summary>
|
||||
Select = 0x4000,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +1,75 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.GamePad
|
||||
namespace Dalamud.Game.ClientState.GamePad;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct GamepadInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct GamepadInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x88)]
|
||||
public int LeftStickX;
|
||||
[FieldOffset(0x88)]
|
||||
public int LeftStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x8C)]
|
||||
public int LeftStickY;
|
||||
/// <summary>
|
||||
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x8C)]
|
||||
public int LeftStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x90)]
|
||||
public int RightStickX;
|
||||
/// <summary>
|
||||
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x90)]
|
||||
public int RightStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x94)]
|
||||
public int RightStickY;
|
||||
/// <summary>
|
||||
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x94)]
|
||||
public int RightStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x98)]
|
||||
public ushort ButtonsRaw;
|
||||
/// <summary>
|
||||
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x98)]
|
||||
public ushort ButtonsRaw;
|
||||
|
||||
/// <summary>
|
||||
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x9C)]
|
||||
public ushort ButtonsPressed;
|
||||
/// <summary>
|
||||
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x9C)]
|
||||
public ushort ButtonsPressed;
|
||||
|
||||
/// <summary>
|
||||
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0xA0)]
|
||||
public ushort ButtonsReleased;
|
||||
/// <summary>
|
||||
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0xA0)]
|
||||
public ushort ButtonsReleased;
|
||||
|
||||
/// <summary>
|
||||
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0xA4)]
|
||||
public ushort ButtonsRepeat;
|
||||
}
|
||||
/// <summary>
|
||||
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0xA4)]
|
||||
public ushort ButtonsRepeat;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,258 +4,257 @@ using Dalamud.Hooking;
|
|||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.GamePad
|
||||
namespace Dalamud.Game.ClientState.GamePad;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the game gamepad state to dalamud.
|
||||
///
|
||||
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
|
||||
/// </summary>
|
||||
public unsafe class GamepadState : IDisposable
|
||||
{
|
||||
private readonly Hook<ControllerPoll> gamepadPoll;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
private int leftStickX;
|
||||
private int leftStickY;
|
||||
private int rightStickX;
|
||||
private int rightStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the game gamepad state to dalamud.
|
||||
///
|
||||
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
|
||||
/// Initializes a new instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
public unsafe class GamepadState : IDisposable
|
||||
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
|
||||
public GamepadState(ClientStateAddressResolver resolver)
|
||||
{
|
||||
private readonly Hook<ControllerPoll> gamepadPoll;
|
||||
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
||||
this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
|
||||
}
|
||||
|
||||
private bool isDisposed;
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
~GamepadState()
|
||||
{
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
private int leftStickX;
|
||||
private int leftStickY;
|
||||
private int rightStickX;
|
||||
private int rightStickY;
|
||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
|
||||
public GamepadState(ClientStateAddressResolver resolver)
|
||||
/// <summary>
|
||||
/// Gets the pointer to the current instance of the GamepadInput struct.
|
||||
/// </summary>
|
||||
public IntPtr GamepadInputAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets buttons pressed bitmask, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsPressed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets raw button bitmask, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets button released bitmask, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsReleased { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets button repeat bitmask, emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsRepeat { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal bool NavEnableGamepad { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> has been pressed.
|
||||
///
|
||||
/// Only true on first frame of the press.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if pressed, 0 otherwise.</returns>
|
||||
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> is being pressed.
|
||||
///
|
||||
/// True in intervals if button is held down.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if still pressed during interval, 0 otherwise or in between intervals.</returns>
|
||||
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> has been released.
|
||||
///
|
||||
/// Only true the frame after release.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if released, 0 otherwise.</returns>
|
||||
public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw state of <paramref name="button"/>.
|
||||
///
|
||||
/// Is set the entire time a button is pressed down.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 the whole time button is pressed, 0 otherwise.</returns>
|
||||
public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Enables the hook of the GamepadPoll function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.gamepadPoll.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private int GamepadPollDetour(IntPtr gamepadInput)
|
||||
{
|
||||
var original = this.gamepadPoll.Original(gamepadInput);
|
||||
try
|
||||
{
|
||||
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
||||
this.gamepadPoll = new Hook<ControllerPoll>(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;
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
~GamepadState()
|
||||
{
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the current instance of the GamepadInput struct.
|
||||
/// </summary>
|
||||
public IntPtr GamepadInputAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets buttons pressed bitmask, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsPressed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets raw button bitmask, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets button released bitmask, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsReleased { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets button repeat bitmask, emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsRepeat { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal bool NavEnableGamepad { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> has been pressed.
|
||||
///
|
||||
/// Only true on first frame of the press.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if pressed, 0 otherwise.</returns>
|
||||
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> is being pressed.
|
||||
///
|
||||
/// True in intervals if button is held down.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if still pressed during interval, 0 otherwise or in between intervals.</returns>
|
||||
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> has been released.
|
||||
///
|
||||
/// Only true the frame after release.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if released, 0 otherwise.</returns>
|
||||
public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw state of <paramref name="button"/>.
|
||||
///
|
||||
/// Is set the entire time a button is pressed down.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 the whole time button is pressed, 0 otherwise.</returns>
|
||||
public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Enables the hook of the GamepadPoll function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.gamepadPoll.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// DRG Blood of the Dragon state types.
|
||||
/// </summary>
|
||||
public enum BOTDState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// DRG Blood of the Dragon state types.
|
||||
/// Inactive type.
|
||||
/// </summary>
|
||||
public enum BOTDState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Inactive type.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Blood of the Dragon is active.
|
||||
/// </summary>
|
||||
BOTD = 1,
|
||||
/// <summary>
|
||||
/// Blood of the Dragon is active.
|
||||
/// </summary>
|
||||
BOTD = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Life of the Dragon is active.
|
||||
/// </summary>
|
||||
LOTD = 2,
|
||||
}
|
||||
/// <summary>
|
||||
/// Life of the Dragon is active.
|
||||
/// </summary>
|
||||
LOTD = 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,52 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// AST Arcanum (card) types.
|
||||
/// </summary>
|
||||
public enum CardType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// AST Arcanum (card) types.
|
||||
/// No card.
|
||||
/// </summary>
|
||||
public enum CardType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No card.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The Balance card.
|
||||
/// </summary>
|
||||
BALANCE = 1,
|
||||
/// <summary>
|
||||
/// The Balance card.
|
||||
/// </summary>
|
||||
BALANCE = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The Bole card.
|
||||
/// </summary>
|
||||
BOLE = 2,
|
||||
/// <summary>
|
||||
/// The Bole card.
|
||||
/// </summary>
|
||||
BOLE = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The Arrow card.
|
||||
/// </summary>
|
||||
ARROW = 3,
|
||||
/// <summary>
|
||||
/// The Arrow card.
|
||||
/// </summary>
|
||||
ARROW = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The Spear card.
|
||||
/// </summary>
|
||||
SPEAR = 4,
|
||||
/// <summary>
|
||||
/// The Spear card.
|
||||
/// </summary>
|
||||
SPEAR = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The Ewer card.
|
||||
/// </summary>
|
||||
EWER = 5,
|
||||
/// <summary>
|
||||
/// The Ewer card.
|
||||
/// </summary>
|
||||
EWER = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The Spire card.
|
||||
/// </summary>
|
||||
SPIRE = 6,
|
||||
/// <summary>
|
||||
/// The Spire card.
|
||||
/// </summary>
|
||||
SPIRE = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The Lord of Crowns card.
|
||||
/// </summary>
|
||||
LORD = 0x70,
|
||||
/// <summary>
|
||||
/// The Lord of Crowns card.
|
||||
/// </summary>
|
||||
LORD = 0x70,
|
||||
|
||||
/// <summary>
|
||||
/// The Lady of Crowns card.
|
||||
/// </summary>
|
||||
LADY = 0x80,
|
||||
}
|
||||
/// <summary>
|
||||
/// The Lady of Crowns card.
|
||||
/// </summary>
|
||||
LADY = 0x80,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// SCH Dismissed fairy types.
|
||||
/// </summary>
|
||||
public enum DismissedFairy : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// SCH Dismissed fairy types.
|
||||
/// Dismissed fairy is Eos.
|
||||
/// </summary>
|
||||
public enum DismissedFairy : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Dismissed fairy is Eos.
|
||||
/// </summary>
|
||||
EOS = 6,
|
||||
EOS = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Dismissed fairy is Selene.
|
||||
/// </summary>
|
||||
SELENE = 7,
|
||||
}
|
||||
/// <summary>
|
||||
/// Dismissed fairy is Selene.
|
||||
/// </summary>
|
||||
SELENE = 7,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// NIN Mudra types.
|
||||
/// </summary>
|
||||
public enum Mudras : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// NIN Mudra types.
|
||||
/// Ten mudra.
|
||||
/// </summary>
|
||||
public enum Mudras : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Ten mudra.
|
||||
/// </summary>
|
||||
TEN = 1,
|
||||
TEN = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Chi mudra.
|
||||
/// </summary>
|
||||
CHI = 2,
|
||||
/// <summary>
|
||||
/// Chi mudra.
|
||||
/// </summary>
|
||||
CHI = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Jin mudra.
|
||||
/// </summary>
|
||||
JIN = 3,
|
||||
}
|
||||
/// <summary>
|
||||
/// Jin mudra.
|
||||
/// </summary>
|
||||
JIN = 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// SMN summoned pet glam types.
|
||||
/// </summary>
|
||||
public enum PetGlam : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// SMN summoned pet glam types.
|
||||
/// No pet glam.
|
||||
/// </summary>
|
||||
public enum PetGlam : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No pet glam.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Emerald carbuncle pet glam.
|
||||
/// </summary>
|
||||
EMERALD = 1,
|
||||
/// <summary>
|
||||
/// Emerald carbuncle pet glam.
|
||||
/// </summary>
|
||||
EMERALD = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Topaz carbuncle pet glam.
|
||||
/// </summary>
|
||||
TOPAZ = 2,
|
||||
/// <summary>
|
||||
/// Topaz carbuncle pet glam.
|
||||
/// </summary>
|
||||
TOPAZ = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Ruby carbuncle pet glam.
|
||||
/// </summary>
|
||||
RUBY = 3,
|
||||
}
|
||||
/// <summary>
|
||||
/// Ruby carbuncle pet glam.
|
||||
/// </summary>
|
||||
RUBY = 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// AST Divination seal types.
|
||||
/// </summary>
|
||||
public enum SealType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// AST Divination seal types.
|
||||
/// No seal.
|
||||
/// </summary>
|
||||
public enum SealType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No seal.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Sun seal.
|
||||
/// </summary>
|
||||
SUN = 1,
|
||||
/// <summary>
|
||||
/// Sun seal.
|
||||
/// </summary>
|
||||
SUN = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Moon seal.
|
||||
/// </summary>
|
||||
MOON = 2,
|
||||
/// <summary>
|
||||
/// Moon seal.
|
||||
/// </summary>
|
||||
MOON = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Celestial seal.
|
||||
/// </summary>
|
||||
CELESTIAL = 3,
|
||||
}
|
||||
/// <summary>
|
||||
/// Celestial seal.
|
||||
/// </summary>
|
||||
CELESTIAL = 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Samurai Sen types.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Sen : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Samurai Sen types.
|
||||
/// No Sen.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Sen : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No Sen.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Setsu Sen type.
|
||||
/// </summary>
|
||||
SETSU = 1 << 0,
|
||||
/// <summary>
|
||||
/// Setsu Sen type.
|
||||
/// </summary>
|
||||
SETSU = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Getsu Sen type.
|
||||
/// </summary>
|
||||
GETSU = 1 << 1,
|
||||
/// <summary>
|
||||
/// Getsu Sen type.
|
||||
/// </summary>
|
||||
GETSU = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Ka Sen type.
|
||||
/// </summary>
|
||||
KA = 1 << 2,
|
||||
}
|
||||
/// <summary>
|
||||
/// Ka Sen type.
|
||||
/// </summary>
|
||||
KA = 1 << 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// BRD Song types.
|
||||
/// </summary>
|
||||
public enum Song : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// BRD Song types.
|
||||
/// No song is active type.
|
||||
/// </summary>
|
||||
public enum Song : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No song is active type.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Mage's Ballad type.
|
||||
/// </summary>
|
||||
MAGE = 5,
|
||||
/// <summary>
|
||||
/// Mage's Ballad type.
|
||||
/// </summary>
|
||||
MAGE = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Army's Paeon type.
|
||||
/// </summary>
|
||||
ARMY = 10,
|
||||
/// <summary>
|
||||
/// Army's Paeon type.
|
||||
/// </summary>
|
||||
ARMY = 10,
|
||||
|
||||
/// <summary>
|
||||
/// The Wanderer's Minuet type.
|
||||
/// </summary>
|
||||
WANDERER = 15,
|
||||
}
|
||||
/// <summary>
|
||||
/// The Wanderer's Minuet type.
|
||||
/// </summary>
|
||||
WANDERER = 15,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// SMN summoned pet types.
|
||||
/// </summary>
|
||||
public enum SummonPet : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// SMN summoned pet types.
|
||||
/// No pet.
|
||||
/// </summary>
|
||||
public enum SummonPet : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No pet.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The summoned pet Ifrit.
|
||||
/// </summary>
|
||||
IFRIT = 3,
|
||||
/// <summary>
|
||||
/// The summoned pet Ifrit.
|
||||
/// </summary>
|
||||
IFRIT = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The summoned pet Titan.
|
||||
/// </summary>
|
||||
TITAN = 4,
|
||||
/// <summary>
|
||||
/// The summoned pet Titan.
|
||||
/// </summary>
|
||||
TITAN = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The summoned pet Garuda.
|
||||
/// </summary>
|
||||
GARUDA = 5,
|
||||
}
|
||||
/// <summary>
|
||||
/// The summoned pet Garuda.
|
||||
/// </summary>
|
||||
GARUDA = 5,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,48 +7,47 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge
|
||||
namespace Dalamud.Game.ClientState.JobGauge;
|
||||
|
||||
/// <summary>
|
||||
/// This class converts in-memory Job gauge data to structs.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public class JobGauges
|
||||
{
|
||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||
|
||||
/// <summary>
|
||||
/// This class converts in-memory Job gauge data to structs.
|
||||
/// Initializes a new instance of the <see cref="JobGauges"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public class JobGauges
|
||||
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
|
||||
public JobGauges(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||
this.Address = addressResolver.JobGaugeData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JobGauges"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
|
||||
public JobGauges(ClientStateAddressResolver addressResolver)
|
||||
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the JobGauge data.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the JobGauge for a given job.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A JobGauge struct from ClientState.Structs.JobGauge.</typeparam>
|
||||
/// <returns>A JobGauge.</returns>
|
||||
public T Get<T>() 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 = 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the JobGauge data.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the JobGauge for a given job.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A JobGauge struct from ClientState.Structs.JobGauge.</typeparam>
|
||||
/// <returns>A JobGauge.</returns>
|
||||
public T Get<T>() 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,39 +2,38 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory AST job gauge.
|
||||
/// </summary>
|
||||
public unsafe class ASTGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.AstrologianGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory AST job gauge.
|
||||
/// Initializes a new instance of the <see cref="ASTGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class ASTGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.AstrologianGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal ASTGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ASTGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal ASTGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently drawn <see cref="CardType"/>.
|
||||
/// </summary>
|
||||
/// <returns>Currently drawn <see cref="CardType"/>.</returns>
|
||||
public CardType DrawnCard => (CardType)this.Struct->Card;
|
||||
/// <summary>
|
||||
/// Gets the currently drawn <see cref="CardType"/>.
|
||||
/// </summary>
|
||||
/// <returns>Currently drawn <see cref="CardType"/>.</returns>
|
||||
public CardType DrawnCard => (CardType)this.Struct->Card;
|
||||
|
||||
/// <summary>
|
||||
/// Check if a <see cref="SealType"/> is currently active on the divination gauge.
|
||||
/// </summary>
|
||||
/// <param name="seal">The <see cref="SealType"/> to check for.</param>
|
||||
/// <returns>If the given Seal is currently divined.</returns>
|
||||
public unsafe bool ContainsSeal(SealType seal)
|
||||
{
|
||||
if (this.Struct->Seals[0] == (byte)seal) return true;
|
||||
if (this.Struct->Seals[1] == (byte)seal) return true;
|
||||
if (this.Struct->Seals[2] == (byte)seal) return true;
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Check if a <see cref="SealType"/> is currently active on the divination gauge.
|
||||
/// </summary>
|
||||
/// <param name="seal">The <see cref="SealType"/> to check for.</param>
|
||||
/// <returns>If the given Seal is currently divined.</returns>
|
||||
public unsafe bool ContainsSeal(SealType seal)
|
||||
{
|
||||
if (this.Struct->Seals[0] == (byte)seal) return true;
|
||||
if (this.Struct->Seals[1] == (byte)seal) return true;
|
||||
if (this.Struct->Seals[2] == (byte)seal) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +1,66 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory BLM job gauge.
|
||||
/// </summary>
|
||||
public unsafe class BLMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BlackMageGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory BLM job gauge.
|
||||
/// Initializes a new instance of the <see cref="BLMGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class BLMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BlackMageGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal BLMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BLMGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal BLMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for the Enochian time in milliseconds.
|
||||
/// </summary>
|
||||
public short EnochianTimer => this.Struct->EnochianTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
|
||||
/// </summary>
|
||||
public short ElementTimeRemaining => this.Struct->ElementTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Polyglot stacks remaining.
|
||||
/// </summary>
|
||||
public byte PolyglotStacks => this.Struct->PolyglotStacks;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Umbral Hearts remaining.
|
||||
/// </summary>
|
||||
public byte UmbralHearts => this.Struct->UmbralHearts;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Umbral Ice stacks.
|
||||
/// </summary>
|
||||
public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Astral Fire stacks.
|
||||
/// </summary>
|
||||
public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if the player is in Umbral Ice.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool InUmbralIce => this.Struct->ElementStance < 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if the player is in Astral fire.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool InAstralFire => this.Struct->ElementStance > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if Enochian is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsEnochianActive => this.Struct->Enochian != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for the Enochian time in milliseconds.
|
||||
/// </summary>
|
||||
public short EnochianTimer => this.Struct->EnochianTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
|
||||
/// </summary>
|
||||
public short ElementTimeRemaining => this.Struct->ElementTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Polyglot stacks remaining.
|
||||
/// </summary>
|
||||
public byte PolyglotStacks => this.Struct->PolyglotStacks;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Umbral Hearts remaining.
|
||||
/// </summary>
|
||||
public byte UmbralHearts => this.Struct->UmbralHearts;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Umbral Ice stacks.
|
||||
/// </summary>
|
||||
public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Astral Fire stacks.
|
||||
/// </summary>
|
||||
public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if the player is in Umbral Ice.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool InUmbralIce => this.Struct->ElementStance < 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if the player is in Astral fire.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool InAstralFire => this.Struct->ElementStance > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if Enochian is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsEnochianActive => this.Struct->Enochian != 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,40 +2,39 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory BRD job gauge.
|
||||
/// </summary>
|
||||
public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BardGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory BRD job gauge.
|
||||
/// Initializes a new instance of the <see cref="BRDGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BardGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal BRDGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BRDGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal BRDGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current song timer in milliseconds.
|
||||
/// </summary>
|
||||
public short SongTimer => this.Struct->SongTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Repertoire accumulated.
|
||||
/// </summary>
|
||||
public byte Repertoire => this.Struct->Repertoire;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Soul Voice accumulated.
|
||||
/// </summary>
|
||||
public byte SoulVoice => this.Struct->SoulVoice;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of song that is active.
|
||||
/// </summary>
|
||||
public Song Song => (Song)this.Struct->Song;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current song timer in milliseconds.
|
||||
/// </summary>
|
||||
public short SongTimer => this.Struct->SongTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Repertoire accumulated.
|
||||
/// </summary>
|
||||
public byte Repertoire => this.Struct->Repertoire;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Soul Voice accumulated.
|
||||
/// </summary>
|
||||
public byte SoulVoice => this.Struct->SoulVoice;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of song that is active.
|
||||
/// </summary>
|
||||
public Song Song => (Song)this.Struct->Song;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,59 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory DNC job gauge.
|
||||
/// </summary>
|
||||
public unsafe class DNCGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DancerGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory DNC job gauge.
|
||||
/// Initializes a new instance of the <see cref="DNCGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class DNCGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DancerGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal DNCGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DNCGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal DNCGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of feathers available.
|
||||
/// </summary>
|
||||
public byte Feathers => this.Struct->Feathers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Espirit available.
|
||||
/// </summary>
|
||||
public byte Esprit => this.Struct->Esprit;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of steps completed for the current dance.
|
||||
/// </summary>
|
||||
public byte CompletedSteps => this.Struct->StepIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the steps in the current dance.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next step in the current dance.
|
||||
/// </summary>
|
||||
/// <returns>The next dance step action ID.</returns>
|
||||
public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player is dancing or not.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsDancing => this.Struct->DanceSteps[0] != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of feathers available.
|
||||
/// </summary>
|
||||
public byte Feathers => this.Struct->Feathers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Espirit available.
|
||||
/// </summary>
|
||||
public byte Esprit => this.Struct->Esprit;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of steps completed for the current dance.
|
||||
/// </summary>
|
||||
public byte CompletedSteps => this.Struct->StepIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the steps in the current dance.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next step in the current dance.
|
||||
/// </summary>
|
||||
/// <returns>The next dance step action ID.</returns>
|
||||
public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player is dancing or not.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsDancing => this.Struct->DanceSteps[0] != 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,35 +2,34 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory DRG job gauge.
|
||||
/// </summary>
|
||||
public unsafe class DRGGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DragoonGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory DRG job gauge.
|
||||
/// Initializes a new instance of the <see cref="DRGGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class DRGGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DragoonGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal DRGGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DRGGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal DRGGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for Blood of the Dragon in milliseconds.
|
||||
/// </summary>
|
||||
public short BOTDTimer => this.Struct->BotdTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current state of Blood of the Dragon.
|
||||
/// </summary>
|
||||
public BOTDState BOTDState => (BOTDState)this.Struct->BotdState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of eyes opened during Blood of the Dragon.
|
||||
/// </summary>
|
||||
public byte EyeCount => this.Struct->EyeCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for Blood of the Dragon in milliseconds.
|
||||
/// </summary>
|
||||
public short BOTDTimer => this.Struct->BotdTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current state of Blood of the Dragon.
|
||||
/// </summary>
|
||||
public BOTDState BOTDState => (BOTDState)this.Struct->BotdState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of eyes opened during Blood of the Dragon.
|
||||
/// </summary>
|
||||
public byte EyeCount => this.Struct->EyeCount;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,39 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory DRK job gauge.
|
||||
/// </summary>
|
||||
public unsafe class DRKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DarkKnightGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory DRK job gauge.
|
||||
/// Initializes a new instance of the <see cref="DRKGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class DRKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DarkKnightGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal DRKGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DRKGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal DRKGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of blood accumulated.
|
||||
/// </summary>
|
||||
public byte Blood => this.Struct->Blood;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Darkside time remaining in milliseconds.
|
||||
/// </summary>
|
||||
public ushort DarksideTimeRemaining => this.Struct->DarksideTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Shadow time remaining in milliseconds.
|
||||
/// </summary>
|
||||
public ushort ShadowTimeRemaining => this.Struct->ShadowTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player has Dark Arts or not.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasDarkArts => this.Struct->DarkArtsState > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of blood accumulated.
|
||||
/// </summary>
|
||||
public byte Blood => this.Struct->Blood;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Darkside time remaining in milliseconds.
|
||||
/// </summary>
|
||||
public ushort DarksideTimeRemaining => this.Struct->DarksideTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Shadow time remaining in milliseconds.
|
||||
/// </summary>
|
||||
public ushort ShadowTimeRemaining => this.Struct->ShadowTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player has Dark Arts or not.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasDarkArts => this.Struct->DarkArtsState > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory GNB job gauge.
|
||||
/// </summary>
|
||||
public unsafe class GNBGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.GunbreakerGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory GNB job gauge.
|
||||
/// Initializes a new instance of the <see cref="GNBGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class GNBGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.GunbreakerGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal GNBGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GNBGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal GNBGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of ammo available.
|
||||
/// </summary>
|
||||
public byte Ammo => this.Struct->Ammo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the max combo time of the Gnashing Fang combo.
|
||||
/// </summary>
|
||||
public short MaxTimerDuration => this.Struct->MaxTimerDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current step of the Gnashing Fang combo.
|
||||
/// </summary>
|
||||
public byte AmmoComboStep => this.Struct->AmmoComboStep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of ammo available.
|
||||
/// </summary>
|
||||
public byte Ammo => this.Struct->Ammo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the max combo time of the Gnashing Fang combo.
|
||||
/// </summary>
|
||||
public short MaxTimerDuration => this.Struct->MaxTimerDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current step of the Gnashing Fang combo.
|
||||
/// </summary>
|
||||
public byte AmmoComboStep => this.Struct->AmmoComboStep;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Base job gauge class.
|
||||
/// </summary>
|
||||
public abstract unsafe class JobGaugeBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Base job gauge class.
|
||||
/// Initializes a new instance of the <see cref="JobGaugeBase"/> class.
|
||||
/// </summary>
|
||||
public abstract unsafe class JobGaugeBase
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal JobGaugeBase(IntPtr address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JobGaugeBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal JobGaugeBase(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this job gauge in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this job gauge in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Base job gauge class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The underlying FFXIVClientStructs type.</typeparam>
|
||||
public unsafe class JobGaugeBase<T> : JobGaugeBase where T : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Base job gauge class.
|
||||
/// Initializes a new instance of the <see cref="JobGaugeBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The underlying FFXIVClientStructs type.</typeparam>
|
||||
public unsafe class JobGaugeBase<T> : JobGaugeBase where T : unmanaged
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal JobGaugeBase(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JobGaugeBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal JobGaugeBase(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsafe struct pointer of this job gauge.
|
||||
/// </summary>
|
||||
private protected T* Struct => (T*)this.Address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsafe struct pointer of this job gauge.
|
||||
/// </summary>
|
||||
private protected T* Struct => (T*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,55 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory MCH job gauge.
|
||||
/// </summary>
|
||||
public unsafe class MCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MachinistGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory MCH job gauge.
|
||||
/// Initializes a new instance of the <see cref="MCHGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class MCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MachinistGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal MCHGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MCHGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal MCHGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time time remaining for Overheat in milliseconds.
|
||||
/// </summary>
|
||||
public short OverheatTimeRemaining => this.Struct->OverheatTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for the Rook or Queen in milliseconds.
|
||||
/// </summary>
|
||||
public short SummonTimeRemaining => this.Struct->SummonTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Heat level.
|
||||
/// </summary>
|
||||
public byte Heat => this.Struct->Heat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Battery level.
|
||||
/// </summary>
|
||||
public byte Battery => this.Struct->Battery;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the battery level of the last summon (robot).
|
||||
/// </summary>
|
||||
public byte LastSummonBatteryPower => this.Struct->LastSummonBatteryPower;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player is currently Overheated.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsOverheated => (this.Struct->TimerActive & 1) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player has an active Robot.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsRobotActive => (this.Struct->TimerActive & 2) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time time remaining for Overheat in milliseconds.
|
||||
/// </summary>
|
||||
public short OverheatTimeRemaining => this.Struct->OverheatTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for the Rook or Queen in milliseconds.
|
||||
/// </summary>
|
||||
public short SummonTimeRemaining => this.Struct->SummonTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Heat level.
|
||||
/// </summary>
|
||||
public byte Heat => this.Struct->Heat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Battery level.
|
||||
/// </summary>
|
||||
public byte Battery => this.Struct->Battery;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the battery level of the last summon (robot).
|
||||
/// </summary>
|
||||
public byte LastSummonBatteryPower => this.Struct->LastSummonBatteryPower;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player is currently Overheated.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsOverheated => (this.Struct->TimerActive & 1) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the player has an active Robot.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsRobotActive => (this.Struct->TimerActive & 2) != 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory MNK job gauge.
|
||||
/// </summary>
|
||||
public unsafe class MNKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MonkGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory MNK job gauge.
|
||||
/// Initializes a new instance of the <see cref="MNKGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class MNKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MonkGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal MNKGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MNKGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal MNKGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Chakra available.
|
||||
/// </summary>
|
||||
public byte Chakra => this.Struct->Chakra;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Chakra available.
|
||||
/// </summary>
|
||||
public byte Chakra => this.Struct->Chakra;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory NIN job gauge.
|
||||
/// </summary>
|
||||
public unsafe class NINGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.NinjaGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory NIN job gauge.
|
||||
/// Initializes a new instance of the <see cref="NINGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class NINGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.NinjaGauge>
|
||||
/// <param name="address">The address of the gauge.</param>
|
||||
internal NINGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NINGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the gauge.</param>
|
||||
internal NINGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time left on Huton in milliseconds.
|
||||
/// </summary>
|
||||
public int HutonTimer => this.Struct->HutonTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Ninki available.
|
||||
/// </summary>
|
||||
public byte Ninki => this.Struct->Ninki;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of times Huton has been cast manually.
|
||||
/// </summary>
|
||||
public byte HutonManualCasts => this.Struct->HutonManualCasts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time left on Huton in milliseconds.
|
||||
/// </summary>
|
||||
public int HutonTimer => this.Struct->HutonTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Ninki available.
|
||||
/// </summary>
|
||||
public byte Ninki => this.Struct->Ninki;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of times Huton has been cast manually.
|
||||
/// </summary>
|
||||
public byte HutonManualCasts => this.Struct->HutonManualCasts;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory PLD job gauge.
|
||||
/// </summary>
|
||||
public unsafe class PLDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.PaladinGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory PLD job gauge.
|
||||
/// Initializes a new instance of the <see cref="PLDGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class PLDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.PaladinGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal PLDGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PLDGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal PLDGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current level of the Oath gauge.
|
||||
/// </summary>
|
||||
public byte OathGauge => this.Struct->OathGauge;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current level of the Oath gauge.
|
||||
/// </summary>
|
||||
public byte OathGauge => this.Struct->OathGauge;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory RDM job gauge.
|
||||
/// </summary>
|
||||
public unsafe class RDMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.RedMageGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory RDM job gauge.
|
||||
/// Initializes a new instance of the <see cref="RDMGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class RDMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.RedMageGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal RDMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RDMGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal RDMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of the White gauge.
|
||||
/// </summary>
|
||||
public byte WhiteMana => this.Struct->WhiteMana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of the Black gauge.
|
||||
/// </summary>
|
||||
public byte BlackMana => this.Struct->BlackMana;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of the White gauge.
|
||||
/// </summary>
|
||||
public byte WhiteMana => this.Struct->WhiteMana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of the Black gauge.
|
||||
/// </summary>
|
||||
public byte BlackMana => this.Struct->BlackMana;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,53 +2,52 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory SAM job gauge.
|
||||
/// </summary>
|
||||
public unsafe class SAMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SamuraiGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory SAM job gauge.
|
||||
/// Initializes a new instance of the <see cref="SAMGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class SAMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SamuraiGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal SAMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SAMGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal SAMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current amount of Kenki available.
|
||||
/// </summary>
|
||||
public byte Kenki => this.Struct->Kenki;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Meditation stacks.
|
||||
/// </summary>
|
||||
public byte MeditationStacks => this.Struct->MeditationStacks;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active Sen.
|
||||
/// </summary>
|
||||
public Sen Sen => (Sen)this.Struct->SenFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Setsu Sen is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasSetsu => (this.Sen & Sen.SETSU) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Getsu Sen is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasGetsu => (this.Sen & Sen.GETSU) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Ka Sen is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasKa => (this.Sen & Sen.KA) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current amount of Kenki available.
|
||||
/// </summary>
|
||||
public byte Kenki => this.Struct->Kenki;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Meditation stacks.
|
||||
/// </summary>
|
||||
public byte MeditationStacks => this.Struct->MeditationStacks;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active Sen.
|
||||
/// </summary>
|
||||
public Sen Sen => (Sen)this.Struct->SenFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Setsu Sen is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasSetsu => (this.Sen & Sen.SETSU) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Getsu Sen is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasGetsu => (this.Sen & Sen.GETSU) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Ka Sen is active.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasKa => (this.Sen & Sen.KA) != 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,40 +2,39 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory SCH job gauge.
|
||||
/// </summary>
|
||||
public unsafe class SCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.ScholarGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory SCH job gauge.
|
||||
/// Initializes a new instance of the <see cref="SCHGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class SCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.ScholarGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal SCHGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SCHGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal SCHGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Aetherflow stacks available.
|
||||
/// </summary>
|
||||
public byte Aetherflow => this.Struct->Aetherflow;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current level of the Fairy Gauge.
|
||||
/// </summary>
|
||||
public byte FairyGauge => this.Struct->FairyGauge;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Seraph time remaiSCHg in milliseconds.
|
||||
/// </summary>
|
||||
public short SeraphTimer => this.Struct->SeraphTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last dismissed fairy.
|
||||
/// </summary>
|
||||
public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of Aetherflow stacks available.
|
||||
/// </summary>
|
||||
public byte Aetherflow => this.Struct->Aetherflow;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current level of the Fairy Gauge.
|
||||
/// </summary>
|
||||
public byte FairyGauge => this.Struct->FairyGauge;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Seraph time remaiSCHg in milliseconds.
|
||||
/// </summary>
|
||||
public short SeraphTimer => this.Struct->SeraphTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last dismissed fairy.
|
||||
/// </summary>
|
||||
public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,59 +2,58 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory SMN job gauge.
|
||||
/// </summary>
|
||||
public unsafe class SMNGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SummonerGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory SMN job gauge.
|
||||
/// Initializes a new instance of the <see cref="SMNGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class SMNGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SummonerGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal SMNGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SMNGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal SMNGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for the current summon.
|
||||
/// </summary>
|
||||
public short TimerRemaining => this.Struct->TimerRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the summon that will return after the current summon expires.
|
||||
/// </summary>
|
||||
public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
|
||||
/// </summary>
|
||||
public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current aether flags.
|
||||
/// Use the summon accessors instead.
|
||||
/// </summary>
|
||||
public byte AetherFlags => this.Struct->AetherFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if Phoenix is ready to be summoned.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsPhoenixReady => (this.AetherFlags & 0x10) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether Bahamut is ready to be summoned.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsBahamutReady => (this.AetherFlags & 8) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there are any Aetherflow stacks available.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasAetherflowStacks => (this.AetherFlags & 3) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for the current summon.
|
||||
/// </summary>
|
||||
public short TimerRemaining => this.Struct->TimerRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the summon that will return after the current summon expires.
|
||||
/// </summary>
|
||||
public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
|
||||
/// </summary>
|
||||
public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current aether flags.
|
||||
/// Use the summon accessors instead.
|
||||
/// </summary>
|
||||
public byte AetherFlags => this.Struct->AetherFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if Phoenix is ready to be summoned.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsPhoenixReady => (this.AetherFlags & 0x10) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether Bahamut is ready to be summoned.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool IsBahamutReady => (this.AetherFlags & 8) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there are any Aetherflow stacks available.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasAetherflowStacks => (this.AetherFlags & 3) > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory WAR job gauge.
|
||||
/// </summary>
|
||||
public unsafe class WARGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WarriorGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory WAR job gauge.
|
||||
/// Initializes a new instance of the <see cref="WARGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class WARGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WarriorGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal WARGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WARGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal WARGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of wrath in the Beast gauge.
|
||||
/// </summary>
|
||||
public byte BeastGauge => this.Struct->BeastGauge;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of wrath in the Beast gauge.
|
||||
/// </summary>
|
||||
public byte BeastGauge => this.Struct->BeastGauge;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory WHM job gauge.
|
||||
/// </summary>
|
||||
public unsafe class WHMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WhiteMageGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory WHM job gauge.
|
||||
/// Initializes a new instance of the <see cref="WHMGauge"/> class.
|
||||
/// </summary>
|
||||
public unsafe class WHMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WhiteMageGauge>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal WHMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WHMGauge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the job gauge.</param>
|
||||
internal WHMGauge(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time to next lily in milliseconds.
|
||||
/// </summary>
|
||||
public short LilyTimer => this.Struct->LilyTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Lilies.
|
||||
/// </summary>
|
||||
public byte Lily => this.Struct->Lily;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of times the blood lily has been nourished.
|
||||
/// </summary>
|
||||
public byte BloodLily => this.Struct->BloodLily;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time to next lily in milliseconds.
|
||||
/// </summary>
|
||||
public short LilyTimer => this.Struct->LilyTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Lilies.
|
||||
/// </summary>
|
||||
public byte Lily => this.Struct->Lily;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of times the blood lily has been nourished.
|
||||
/// </summary>
|
||||
public byte BloodLily => this.Struct->BloodLily;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,155 +6,154 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Keys
|
||||
namespace Dalamud.Game.ClientState.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting
|
||||
/// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without
|
||||
/// setting the statue manually.
|
||||
/// index & 0 = key pressed.
|
||||
/// index & 1 = key down (ephemeral).
|
||||
/// index & 2 = key up (ephemeral).
|
||||
/// index & 3 = short key press (ephemeral).
|
||||
/// </remarks>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public class KeyState
|
||||
{
|
||||
// The array is accessed in a way that this limit doesn't appear to exist
|
||||
// but there is other state data past this point, and keys beyond here aren't
|
||||
// generally valid for most things anyway
|
||||
private const int MaxKeyCode = 0xF0;
|
||||
private readonly IntPtr bufferBase;
|
||||
private readonly IntPtr indexBase;
|
||||
private VirtualKey[] validVirtualKeyCache = null;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
|
||||
/// Initializes a new instance of the <see cref="KeyState"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting
|
||||
/// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without
|
||||
/// setting the statue manually.
|
||||
/// index & 0 = key pressed.
|
||||
/// index & 1 = key down (ephemeral).
|
||||
/// index & 2 = key up (ephemeral).
|
||||
/// index & 3 = short key press (ephemeral).
|
||||
/// </remarks>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public class KeyState
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
public KeyState(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
// The array is accessed in a way that this limit doesn't appear to exist
|
||||
// but there is other state data past this point, and keys beyond here aren't
|
||||
// generally valid for most things anyway
|
||||
private const int MaxKeyCode = 0xF0;
|
||||
private readonly IntPtr bufferBase;
|
||||
private readonly IntPtr indexBase;
|
||||
private VirtualKey[] validVirtualKeyCache = null;
|
||||
var moduleBaseAddress = Service<SigScanner>.Get().Module.BaseAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="KeyState"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
public KeyState(ClientStateAddressResolver addressResolver)
|
||||
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
||||
this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);
|
||||
|
||||
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the key-pressed state for a given vkCode.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">The virtual key to change.</param>
|
||||
/// <returns>Whether the specified key is currently pressed.</returns>
|
||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
||||
public unsafe bool this[int vkCode]
|
||||
{
|
||||
get => this.GetRawValue(vkCode) != 0;
|
||||
set => this.SetRawValue(vkCode, value ? 1 : 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="this[int]"/>
|
||||
public bool this[VirtualKey vkCode]
|
||||
{
|
||||
get => this[(int)vkCode];
|
||||
set => this[(int)vkCode] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value in the index array.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">The virtual key to change.</param>
|
||||
/// <returns>The raw value stored in the index array.</returns>
|
||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||
public int GetRawValue(int vkCode)
|
||||
=> this.GetRefValue(vkCode);
|
||||
|
||||
/// <inheritdoc cref="GetRawValue(int)"/>
|
||||
public int GetRawValue(VirtualKey vkCode)
|
||||
=> this.GetRawValue((int)vkCode);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value in the index array.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">The virtual key to change.</param>
|
||||
/// <param name="value">The raw value to set in the index array.</param>
|
||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
||||
public void SetRawValue(int vkCode, int value)
|
||||
{
|
||||
if (value != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team.");
|
||||
|
||||
this.GetRefValue(vkCode) = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRawValue(int, int)"/>
|
||||
public void SetRawValue(VirtualKey vkCode, int value)
|
||||
=> this.SetRawValue((int)vkCode, value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">Virtual key code.</param>
|
||||
/// <returns>If the code is valid.</returns>
|
||||
public bool IsVirtualKeyValid(int vkCode)
|
||||
=> this.ConvertVirtualKey(vkCode) != 0;
|
||||
|
||||
/// <inheritdoc cref="IsVirtualKeyValid(int)"/>
|
||||
public bool IsVirtualKeyValid(VirtualKey vkCode)
|
||||
=> this.IsVirtualKeyValid((int)vkCode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of virtual keys the game considers valid input.
|
||||
/// </summary>
|
||||
/// <returns>An array of valid virtual keys.</returns>
|
||||
public VirtualKey[] GetValidVirtualKeys()
|
||||
=> this.validVirtualKeyCache ??= Enum.GetValues<VirtualKey>().Where(vk => this.IsVirtualKeyValid(vk)).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the pressed state for all keys.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
foreach (var vk in this.GetValidVirtualKeys())
|
||||
{
|
||||
var moduleBaseAddress = Service<SigScanner>.Get().Module.BaseAddress;
|
||||
|
||||
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
||||
this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);
|
||||
|
||||
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the key-pressed state for a given vkCode.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">The virtual key to change.</param>
|
||||
/// <returns>Whether the specified key is currently pressed.</returns>
|
||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
||||
public unsafe bool this[int vkCode]
|
||||
{
|
||||
get => this.GetRawValue(vkCode) != 0;
|
||||
set => this.SetRawValue(vkCode, value ? 1 : 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="this[int]"/>
|
||||
public bool this[VirtualKey vkCode]
|
||||
{
|
||||
get => this[(int)vkCode];
|
||||
set => this[(int)vkCode] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value in the index array.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">The virtual key to change.</param>
|
||||
/// <returns>The raw value stored in the index array.</returns>
|
||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||
public int GetRawValue(int vkCode)
|
||||
=> this.GetRefValue(vkCode);
|
||||
|
||||
/// <inheritdoc cref="GetRawValue(int)"/>
|
||||
public int GetRawValue(VirtualKey vkCode)
|
||||
=> this.GetRawValue((int)vkCode);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value in the index array.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">The virtual key to change.</param>
|
||||
/// <param name="value">The raw value to set in the index array.</param>
|
||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
||||
public void SetRawValue(int vkCode, int value)
|
||||
{
|
||||
if (value != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team.");
|
||||
|
||||
this.GetRefValue(vkCode) = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRawValue(int, int)"/>
|
||||
public void SetRawValue(VirtualKey vkCode, int value)
|
||||
=> this.SetRawValue((int)vkCode, value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">Virtual key code.</param>
|
||||
/// <returns>If the code is valid.</returns>
|
||||
public bool IsVirtualKeyValid(int vkCode)
|
||||
=> this.ConvertVirtualKey(vkCode) != 0;
|
||||
|
||||
/// <inheritdoc cref="IsVirtualKeyValid(int)"/>
|
||||
public bool IsVirtualKeyValid(VirtualKey vkCode)
|
||||
=> this.IsVirtualKeyValid((int)vkCode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of virtual keys the game considers valid input.
|
||||
/// </summary>
|
||||
/// <returns>An array of valid virtual keys.</returns>
|
||||
public VirtualKey[] GetValidVirtualKeys()
|
||||
=> this.validVirtualKeyCache ??= Enum.GetValues<VirtualKey>().Where(vk => this.IsVirtualKeyValid(vk)).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the pressed state for all keys.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
foreach (var vk in this.GetValidVirtualKeys())
|
||||
{
|
||||
this[vk] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a virtual key into the equivalent value that the game uses.
|
||||
/// Valid values are non-zero.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">Virtual key.</param>
|
||||
/// <returns>Converted value.</returns>
|
||||
private unsafe byte ConvertVirtualKey(int vkCode)
|
||||
{
|
||||
if (vkCode <= 0 || vkCode >= MaxKeyCode)
|
||||
return 0;
|
||||
|
||||
return *(byte*)(this.indexBase + vkCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw value from the key state array.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">Virtual key code.</param>
|
||||
/// <returns>A reference to the indexed array.</returns>
|
||||
private unsafe ref int GetRefValue(int vkCode)
|
||||
{
|
||||
vkCode = this.ConvertVirtualKey(vkCode);
|
||||
|
||||
if (vkCode == 0)
|
||||
throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help.");
|
||||
|
||||
return ref *(int*)(this.bufferBase + (4 * vkCode));
|
||||
this[vk] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a virtual key into the equivalent value that the game uses.
|
||||
/// Valid values are non-zero.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">Virtual key.</param>
|
||||
/// <returns>Converted value.</returns>
|
||||
private unsafe byte ConvertVirtualKey(int vkCode)
|
||||
{
|
||||
if (vkCode <= 0 || vkCode >= MaxKeyCode)
|
||||
return 0;
|
||||
|
||||
return *(byte*)(this.indexBase + vkCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw value from the key state array.
|
||||
/// </summary>
|
||||
/// <param name="vkCode">Virtual key code.</param>
|
||||
/// <returns>A reference to the indexed array.</returns>
|
||||
private unsafe ref int GetRefValue(int vkCode)
|
||||
{
|
||||
vkCode = this.ConvertVirtualKey(vkCode);
|
||||
|
||||
if (vkCode == 0)
|
||||
throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help.");
|
||||
|
||||
return ref *(int*)(this.bufferBase + (4 * vkCode));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,28 +1,27 @@
|
|||
namespace Dalamud.Game.ClientState.Objects.Enums
|
||||
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// An Enum describing possible BattleNpc kinds.
|
||||
/// </summary>
|
||||
public enum BattleNpcSubKind : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// An Enum describing possible BattleNpc kinds.
|
||||
/// Invalid BattleNpc.
|
||||
/// </summary>
|
||||
public enum BattleNpcSubKind : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid BattleNpc.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// BattleNpc representing a Pet.
|
||||
/// </summary>
|
||||
Pet = 2,
|
||||
/// <summary>
|
||||
/// BattleNpc representing a Pet.
|
||||
/// </summary>
|
||||
Pet = 2,
|
||||
|
||||
/// <summary>
|
||||
/// BattleNpc representing a Chocobo.
|
||||
/// </summary>
|
||||
Chocobo = 3,
|
||||
/// <summary>
|
||||
/// BattleNpc representing a Chocobo.
|
||||
/// </summary>
|
||||
Chocobo = 3,
|
||||
|
||||
/// <summary>
|
||||
/// BattleNpc representing a standard enemy.
|
||||
/// </summary>
|
||||
Enemy = 5,
|
||||
}
|
||||
/// <summary>
|
||||
/// BattleNpc representing a standard enemy.
|
||||
/// </summary>
|
||||
Enemy = 5,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,139 +1,138 @@
|
|||
namespace Dalamud.Game.ClientState.Objects.Enums
|
||||
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// This enum describes the indices of the Customize array.
|
||||
/// </summary>
|
||||
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
|
||||
public enum CustomizeIndex
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum describes the indices of the Customize array.
|
||||
/// The race of the character.
|
||||
/// </summary>
|
||||
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
|
||||
public enum CustomizeIndex
|
||||
{
|
||||
/// <summary>
|
||||
/// The race of the character.
|
||||
/// </summary>
|
||||
Race = 0x00,
|
||||
Race = 0x00,
|
||||
|
||||
/// <summary>
|
||||
/// The gender of the character.
|
||||
/// </summary>
|
||||
Gender = 0x01,
|
||||
/// <summary>
|
||||
/// The gender of the character.
|
||||
/// </summary>
|
||||
Gender = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// The tribe of the character.
|
||||
/// </summary>
|
||||
Tribe = 0x04,
|
||||
/// <summary>
|
||||
/// The tribe of the character.
|
||||
/// </summary>
|
||||
Tribe = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// The height of the character.
|
||||
/// </summary>
|
||||
Height = 0x03,
|
||||
/// <summary>
|
||||
/// The height of the character.
|
||||
/// </summary>
|
||||
Height = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// The model type of the character.
|
||||
/// </summary>
|
||||
ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features
|
||||
/// <summary>
|
||||
/// The model type of the character.
|
||||
/// </summary>
|
||||
ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features
|
||||
|
||||
/// <summary>
|
||||
/// The face type of the character.
|
||||
/// </summary>
|
||||
FaceType = 0x05,
|
||||
/// <summary>
|
||||
/// The face type of the character.
|
||||
/// </summary>
|
||||
FaceType = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// The hair of the character.
|
||||
/// </summary>
|
||||
HairStyle = 0x06,
|
||||
/// <summary>
|
||||
/// The hair of the character.
|
||||
/// </summary>
|
||||
HairStyle = 0x06,
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the character has hair highlights.
|
||||
/// </summary>
|
||||
HasHighlights = 0x07, // negative to enable, positive to disable
|
||||
/// <summary>
|
||||
/// Whether or not the character has hair highlights.
|
||||
/// </summary>
|
||||
HasHighlights = 0x07, // negative to enable, positive to disable
|
||||
|
||||
/// <summary>
|
||||
/// The skin color of the character.
|
||||
/// </summary>
|
||||
SkinColor = 0x08,
|
||||
/// <summary>
|
||||
/// The skin color of the character.
|
||||
/// </summary>
|
||||
SkinColor = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// The eye color of the character.
|
||||
/// </summary>
|
||||
EyeColor = 0x09, // color of character's right eye
|
||||
/// <summary>
|
||||
/// The eye color of the character.
|
||||
/// </summary>
|
||||
EyeColor = 0x09, // color of character's right eye
|
||||
|
||||
/// <summary>
|
||||
/// The hair color of the character.
|
||||
/// </summary>
|
||||
HairColor = 0x0A, // main color
|
||||
/// <summary>
|
||||
/// The hair color of the character.
|
||||
/// </summary>
|
||||
HairColor = 0x0A, // main color
|
||||
|
||||
/// <summary>
|
||||
/// The highlights hair color of the character.
|
||||
/// </summary>
|
||||
HairColor2 = 0x0B, // highlights color
|
||||
/// <summary>
|
||||
/// The highlights hair color of the character.
|
||||
/// </summary>
|
||||
HairColor2 = 0x0B, // highlights color
|
||||
|
||||
/// <summary>
|
||||
/// The face features of the character.
|
||||
/// </summary>
|
||||
FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small
|
||||
/// <summary>
|
||||
/// The face features of the character.
|
||||
/// </summary>
|
||||
FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small
|
||||
|
||||
/// <summary>
|
||||
/// The color of the face features of the character.
|
||||
/// </summary>
|
||||
FaceFeaturesColor = 0x0D,
|
||||
/// <summary>
|
||||
/// The color of the face features of the character.
|
||||
/// </summary>
|
||||
FaceFeaturesColor = 0x0D,
|
||||
|
||||
/// <summary>
|
||||
/// The eyebrows of the character.
|
||||
/// </summary>
|
||||
Eyebrows = 0x0E,
|
||||
/// <summary>
|
||||
/// The eyebrows of the character.
|
||||
/// </summary>
|
||||
Eyebrows = 0x0E,
|
||||
|
||||
/// <summary>
|
||||
/// The 2nd eye color of the character.
|
||||
/// </summary>
|
||||
EyeColor2 = 0x0F, // color of character's left eye
|
||||
/// <summary>
|
||||
/// The 2nd eye color of the character.
|
||||
/// </summary>
|
||||
EyeColor2 = 0x0F, // color of character's left eye
|
||||
|
||||
/// <summary>
|
||||
/// The eye shape of the character.
|
||||
/// </summary>
|
||||
EyeShape = 0x10,
|
||||
/// <summary>
|
||||
/// The eye shape of the character.
|
||||
/// </summary>
|
||||
EyeShape = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// The nose shape of the character.
|
||||
/// </summary>
|
||||
NoseShape = 0x11,
|
||||
/// <summary>
|
||||
/// The nose shape of the character.
|
||||
/// </summary>
|
||||
NoseShape = 0x11,
|
||||
|
||||
/// <summary>
|
||||
/// The jaw shape of the character.
|
||||
/// </summary>
|
||||
JawShape = 0x12,
|
||||
/// <summary>
|
||||
/// The jaw shape of the character.
|
||||
/// </summary>
|
||||
JawShape = 0x12,
|
||||
|
||||
/// <summary>
|
||||
/// The lip style of the character.
|
||||
/// </summary>
|
||||
LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour)
|
||||
/// <summary>
|
||||
/// The lip style of the character.
|
||||
/// </summary>
|
||||
LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour)
|
||||
|
||||
/// <summary>
|
||||
/// The lip color of the character.
|
||||
/// </summary>
|
||||
LipColor = 0x14,
|
||||
/// <summary>
|
||||
/// The lip color of the character.
|
||||
/// </summary>
|
||||
LipColor = 0x14,
|
||||
|
||||
/// <summary>
|
||||
/// The race feature size of the character.
|
||||
/// </summary>
|
||||
RaceFeatureSize = 0x15,
|
||||
/// <summary>
|
||||
/// The race feature size of the character.
|
||||
/// </summary>
|
||||
RaceFeatureSize = 0x15,
|
||||
|
||||
/// <summary>
|
||||
/// The race feature type of the character.
|
||||
/// </summary>
|
||||
RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client
|
||||
/// <summary>
|
||||
/// The race feature type of the character.
|
||||
/// </summary>
|
||||
RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client
|
||||
|
||||
/// <summary>
|
||||
/// The bust size of the character.
|
||||
/// </summary>
|
||||
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
|
||||
/// <summary>
|
||||
/// The bust size of the character.
|
||||
/// </summary>
|
||||
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
|
||||
|
||||
/// <summary>
|
||||
/// The face paint of the character.
|
||||
/// </summary>
|
||||
Facepaint = 0x18,
|
||||
/// <summary>
|
||||
/// The face paint of the character.
|
||||
/// </summary>
|
||||
Facepaint = 0x18,
|
||||
|
||||
/// <summary>
|
||||
/// The face paint color of the character.
|
||||
/// </summary>
|
||||
FacepaintColor = 0x19,
|
||||
}
|
||||
/// <summary>
|
||||
/// The face paint color of the character.
|
||||
/// </summary>
|
||||
FacepaintColor = 0x19,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,82 @@
|
|||
namespace Dalamud.Game.ClientState.Objects.Enums
|
||||
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Enum describing possible entity kinds.
|
||||
/// </summary>
|
||||
public enum ObjectKind : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum describing possible entity kinds.
|
||||
/// Invalid character.
|
||||
/// </summary>
|
||||
public enum ObjectKind : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid character.
|
||||
/// </summary>
|
||||
None = 0x00,
|
||||
None = 0x00,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing player characters.
|
||||
/// </summary>
|
||||
Player = 0x01,
|
||||
/// <summary>
|
||||
/// Objects representing player characters.
|
||||
/// </summary>
|
||||
Player = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing battle NPCs.
|
||||
/// </summary>
|
||||
BattleNpc = 0x02,
|
||||
/// <summary>
|
||||
/// Objects representing battle NPCs.
|
||||
/// </summary>
|
||||
BattleNpc = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing event NPCs.
|
||||
/// </summary>
|
||||
EventNpc = 0x03,
|
||||
/// <summary>
|
||||
/// Objects representing event NPCs.
|
||||
/// </summary>
|
||||
EventNpc = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing treasures.
|
||||
/// </summary>
|
||||
Treasure = 0x04,
|
||||
/// <summary>
|
||||
/// Objects representing treasures.
|
||||
/// </summary>
|
||||
Treasure = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing aetherytes.
|
||||
/// </summary>
|
||||
Aetheryte = 0x05,
|
||||
/// <summary>
|
||||
/// Objects representing aetherytes.
|
||||
/// </summary>
|
||||
Aetheryte = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing gathering points.
|
||||
/// </summary>
|
||||
GatheringPoint = 0x06,
|
||||
/// <summary>
|
||||
/// Objects representing gathering points.
|
||||
/// </summary>
|
||||
GatheringPoint = 0x06,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing event objects.
|
||||
/// </summary>
|
||||
EventObj = 0x07,
|
||||
/// <summary>
|
||||
/// Objects representing event objects.
|
||||
/// </summary>
|
||||
EventObj = 0x07,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing mounts.
|
||||
/// </summary>
|
||||
MountType = 0x08,
|
||||
/// <summary>
|
||||
/// Objects representing mounts.
|
||||
/// </summary>
|
||||
MountType = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing minions.
|
||||
/// </summary>
|
||||
Companion = 0x09, // Minion
|
||||
/// <summary>
|
||||
/// Objects representing minions.
|
||||
/// </summary>
|
||||
Companion = 0x09, // Minion
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing retainers.
|
||||
/// </summary>
|
||||
Retainer = 0x0A,
|
||||
/// <summary>
|
||||
/// Objects representing retainers.
|
||||
/// </summary>
|
||||
Retainer = 0x0A,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing area objects.
|
||||
/// </summary>
|
||||
Area = 0x0B,
|
||||
/// <summary>
|
||||
/// Objects representing area objects.
|
||||
/// </summary>
|
||||
Area = 0x0B,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing housing objects.
|
||||
/// </summary>
|
||||
Housing = 0x0C,
|
||||
/// <summary>
|
||||
/// Objects representing housing objects.
|
||||
/// </summary>
|
||||
Housing = 0x0C,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing cutscene objects.
|
||||
/// </summary>
|
||||
Cutscene = 0x0D,
|
||||
/// <summary>
|
||||
/// Objects representing cutscene objects.
|
||||
/// </summary>
|
||||
Cutscene = 0x0D,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing card stand objects.
|
||||
/// </summary>
|
||||
CardStand = 0x0E,
|
||||
}
|
||||
/// <summary>
|
||||
/// Objects representing card stand objects.
|
||||
/// </summary>
|
||||
CardStand = 0x0E,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,55 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Enums
|
||||
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Enum describing possible status flags.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum StatusFlags : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum describing possible status flags.
|
||||
/// No status flags set.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum StatusFlags : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No status flags set.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Hostile character.
|
||||
/// </summary>
|
||||
Hostile = 1,
|
||||
/// <summary>
|
||||
/// Hostile character.
|
||||
/// </summary>
|
||||
Hostile = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Character in combat.
|
||||
/// </summary>
|
||||
InCombat = 2,
|
||||
/// <summary>
|
||||
/// Character in combat.
|
||||
/// </summary>
|
||||
InCombat = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Character weapon is out.
|
||||
/// </summary>
|
||||
WeaponOut = 4,
|
||||
/// <summary>
|
||||
/// Character weapon is out.
|
||||
/// </summary>
|
||||
WeaponOut = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Character offhand is out.
|
||||
/// </summary>
|
||||
OffhandOut = 8,
|
||||
/// <summary>
|
||||
/// Character offhand is out.
|
||||
/// </summary>
|
||||
OffhandOut = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Character is a party member.
|
||||
/// </summary>
|
||||
PartyMember = 16,
|
||||
/// <summary>
|
||||
/// Character is a party member.
|
||||
/// </summary>
|
||||
PartyMember = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Character is a alliance member.
|
||||
/// </summary>
|
||||
AllianceMember = 32,
|
||||
/// <summary>
|
||||
/// Character is a alliance member.
|
||||
/// </summary>
|
||||
AllianceMember = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Character is in friend list.
|
||||
/// </summary>
|
||||
Friend = 64,
|
||||
/// <summary>
|
||||
/// Character is in friend list.
|
||||
/// </summary>
|
||||
Friend = 64,
|
||||
|
||||
/// <summary>
|
||||
/// Character is casting.
|
||||
/// </summary>
|
||||
IsCasting = 128,
|
||||
}
|
||||
/// <summary>
|
||||
/// Character is casting.
|
||||
/// </summary>
|
||||
IsCasting = 128,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,140 +9,139 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects
|
||||
namespace Dalamud.Game.ClientState.Objects;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV game objects.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class ObjectTable
|
||||
{
|
||||
private const int ObjectTableLength = 424;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV game objects.
|
||||
/// Initializes a new instance of the <see cref="ObjectTable"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed partial class ObjectTable
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal ObjectTable(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
private const int ObjectTableLength = 424;
|
||||
this.address = addressResolver;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal ObjectTable(ClientStateAddressResolver addressResolver)
|
||||
/// <summary>
|
||||
/// Gets the address of the object table.
|
||||
/// </summary>
|
||||
public IntPtr Address => this.address.ObjectTable;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the object table.
|
||||
/// </summary>
|
||||
public int Length => ObjectTableLength;
|
||||
|
||||
/// <summary>
|
||||
/// Get an object at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>An <see cref="GameObject"/> at the specified spawn index.</returns>
|
||||
public GameObject? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
this.address = addressResolver;
|
||||
|
||||
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the object table.
|
||||
/// </summary>
|
||||
public IntPtr Address => this.address.ObjectTable;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the object table.
|
||||
/// </summary>
|
||||
public int Length => ObjectTableLength;
|
||||
|
||||
/// <summary>
|
||||
/// Get an object at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>An <see cref="GameObject"/> at the specified spawn index.</returns>
|
||||
public GameObject? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var address = this.GetObjectAddress(index);
|
||||
return this.CreateObjectReference(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for a game object by their Object ID.
|
||||
/// </summary>
|
||||
/// <param name="objectId">Object ID to find.</param>
|
||||
/// <returns>A game object or null.</returns>
|
||||
public GameObject? SearchById(uint objectId)
|
||||
{
|
||||
if (objectId is GameObject.InvalidGameObjectId or 0)
|
||||
return null;
|
||||
|
||||
foreach (var obj in this)
|
||||
{
|
||||
if (obj == null)
|
||||
continue;
|
||||
|
||||
if (obj.ObjectId == objectId)
|
||||
return obj;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the game object at the specified index of the object table.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the object.</param>
|
||||
/// <returns>The memory address of the object.</returns>
|
||||
public unsafe IntPtr GetObjectAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= ObjectTableLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return *(IntPtr*)(this.address.ObjectTable + (8 * index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV game object.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the object in memory.</param>
|
||||
/// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns>
|
||||
public unsafe GameObject? CreateObjectReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address;
|
||||
var objKind = (ObjectKind)obj->ObjectKind;
|
||||
return objKind switch
|
||||
{
|
||||
ObjectKind.Player => new PlayerCharacter(address),
|
||||
ObjectKind.BattleNpc => new BattleNpc(address),
|
||||
ObjectKind.EventObj => new EventObj(address),
|
||||
ObjectKind.Companion => new Npc(address),
|
||||
_ => new GameObject(address),
|
||||
};
|
||||
var address = this.GetObjectAddress(index);
|
||||
return this.CreateObjectReference(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV game objects.
|
||||
/// Search for a game object by their Object ID.
|
||||
/// </summary>
|
||||
public sealed partial class ObjectTable : IReadOnlyCollection<GameObject>
|
||||
/// <param name="objectId">Object ID to find.</param>
|
||||
/// <returns>A game object or null.</returns>
|
||||
public GameObject? SearchById(uint objectId)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<GameObject>.Count => this.Length;
|
||||
if (objectId is GameObject.InvalidGameObjectId or 0)
|
||||
return null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<GameObject> GetEnumerator()
|
||||
foreach (var obj in this)
|
||||
{
|
||||
for (var i = 0; i < ObjectTableLength; i++)
|
||||
{
|
||||
var obj = this[i];
|
||||
if (obj == null)
|
||||
continue;
|
||||
|
||||
if (obj == null)
|
||||
continue;
|
||||
|
||||
yield return obj;
|
||||
}
|
||||
if (obj.ObjectId == objectId)
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the game object at the specified index of the object table.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the object.</param>
|
||||
/// <returns>The memory address of the object.</returns>
|
||||
public unsafe IntPtr GetObjectAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= ObjectTableLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return *(IntPtr*)(this.address.ObjectTable + (8 * index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV game object.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the object in memory.</param>
|
||||
/// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns>
|
||||
public unsafe GameObject? CreateObjectReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address;
|
||||
var objKind = (ObjectKind)obj->ObjectKind;
|
||||
return objKind switch
|
||||
{
|
||||
ObjectKind.Player => new PlayerCharacter(address),
|
||||
ObjectKind.BattleNpc => new BattleNpc(address),
|
||||
ObjectKind.EventObj => new EventObj(address),
|
||||
ObjectKind.Companion => new Npc(address),
|
||||
_ => new GameObject(address),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV game objects.
|
||||
/// </summary>
|
||||
public sealed partial class ObjectTable : IReadOnlyCollection<GameObject>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<GameObject>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<GameObject> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < ObjectTableLength; i++)
|
||||
{
|
||||
var obj = this[i];
|
||||
|
||||
if (obj == null)
|
||||
continue;
|
||||
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,29 +2,28 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a battle NPC.
|
||||
/// </summary>
|
||||
public unsafe class BattleNpc : BattleChara
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a battle NPC.
|
||||
/// Initializes a new instance of the <see cref="BattleNpc"/> class.
|
||||
/// Set up a new BattleNpc with the provided memory representation.
|
||||
/// </summary>
|
||||
public unsafe class BattleNpc : BattleChara
|
||||
/// <param name="address">The address of this actor in memory.</param>
|
||||
internal BattleNpc(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BattleNpc"/> class.
|
||||
/// Set up a new BattleNpc with the provided memory representation.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this actor in memory.</param>
|
||||
internal BattleNpc(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
|
||||
/// </summary>
|
||||
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint TargetObjectId => this.Struct->Character.TargetObjectID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
|
||||
/// </summary>
|
||||
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint TargetObjectId => this.Struct->Character.TargetObjectID;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,20 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.SubKinds
|
||||
namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an EventObj.
|
||||
/// </summary>
|
||||
public unsafe class EventObj : GameObject
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents an EventObj.
|
||||
/// Initializes a new instance of the <see cref="EventObj"/> class.
|
||||
/// Set up a new EventObj with the provided memory representation.
|
||||
/// </summary>
|
||||
public unsafe class EventObj : GameObject
|
||||
/// <param name="address">The address of this event object in memory.</param>
|
||||
internal EventObj(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EventObj"/> class.
|
||||
/// Set up a new EventObj with the provided memory representation.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this event object in memory.</param>
|
||||
internal EventObj(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,20 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.SubKinds
|
||||
namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a NPC.
|
||||
/// </summary>
|
||||
public unsafe class Npc : Character
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a NPC.
|
||||
/// Initializes a new instance of the <see cref="Npc"/> class.
|
||||
/// Set up a new NPC with the provided memory representation.
|
||||
/// </summary>
|
||||
public unsafe class Npc : Character
|
||||
/// <param name="address">The address of this actor in memory.</param>
|
||||
internal Npc(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Npc"/> class.
|
||||
/// Set up a new NPC with the provided memory representation.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this actor in memory.</param>
|
||||
internal Npc(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,36 +3,35 @@ using System;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.SubKinds
|
||||
namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a player character.
|
||||
/// </summary>
|
||||
public unsafe class PlayerCharacter : BattleChara
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a player character.
|
||||
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
|
||||
/// This represents a player character.
|
||||
/// </summary>
|
||||
public unsafe class PlayerCharacter : BattleChara
|
||||
/// <param name="address">The address of this actor in memory.</param>
|
||||
internal PlayerCharacter(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
|
||||
/// This represents a player character.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this actor in memory.</param>
|
||||
internal PlayerCharacter(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->Character.CurrentWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->Character.HomeWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target actor ID of the PlayerCharacter.
|
||||
/// </summary>
|
||||
public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->Character.CurrentWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->Character.HomeWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target actor ID of the PlayerCharacter.
|
||||
/// </summary>
|
||||
public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,161 +4,160 @@ using Dalamud.Game.ClientState.Objects.Types;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects
|
||||
namespace Dalamud.Game.ClientState.Objects;
|
||||
|
||||
/// <summary>
|
||||
/// Get and set various kinds of targets for the player.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed unsafe class TargetManager
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Get and set various kinds of targets for the player.
|
||||
/// Initializes a new instance of the <see cref="TargetManager"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed unsafe class TargetManager
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
internal TargetManager(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TargetManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
internal TargetManager(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the target manager.
|
||||
/// </summary>
|
||||
public IntPtr Address => this.address.TargetManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current target.
|
||||
/// </summary>
|
||||
public GameObject? Target
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->Target);
|
||||
set => this.SetTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mouseover target.
|
||||
/// </summary>
|
||||
public GameObject? MouseOverTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
||||
set => this.SetMouseOverTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the focus target.
|
||||
/// </summary>
|
||||
public GameObject? FocusTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->FocusTarget);
|
||||
set => this.SetFocusTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the previous target.
|
||||
/// </summary>
|
||||
public GameObject? PreviousTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
||||
set => this.SetPreviousTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the soft target.
|
||||
/// </summary>
|
||||
public GameObject? SoftTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->SoftTarget);
|
||||
set => this.SetSoftTarget(value);
|
||||
}
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mouseover target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetMouseOverTarget(GameObject? actor) => this.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the focus target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the previous target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetPreviousTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the soft target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetSoftTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mouseover target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the focus target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the previous target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the soft target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current target.
|
||||
/// </summary>
|
||||
public void ClearTarget() => this.SetTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the mouseover target.
|
||||
/// </summary>
|
||||
public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the focus target.
|
||||
/// </summary>
|
||||
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previous target.
|
||||
/// </summary>
|
||||
public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the soft target.
|
||||
/// </summary>
|
||||
public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero);
|
||||
this.address = addressResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the target manager.
|
||||
/// </summary>
|
||||
public IntPtr Address => this.address.TargetManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current target.
|
||||
/// </summary>
|
||||
public GameObject? Target
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->Target);
|
||||
set => this.SetTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mouseover target.
|
||||
/// </summary>
|
||||
public GameObject? MouseOverTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
||||
set => this.SetMouseOverTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the focus target.
|
||||
/// </summary>
|
||||
public GameObject? FocusTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->FocusTarget);
|
||||
set => this.SetFocusTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the previous target.
|
||||
/// </summary>
|
||||
public GameObject? PreviousTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
||||
set => this.SetPreviousTarget(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the soft target.
|
||||
/// </summary>
|
||||
public GameObject? SoftTarget
|
||||
{
|
||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->SoftTarget);
|
||||
set => this.SetSoftTarget(value);
|
||||
}
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mouseover target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetMouseOverTarget(GameObject? actor) => this.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the focus target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the previous target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetPreviousTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the soft target.
|
||||
/// </summary>
|
||||
/// <param name="actor">Actor to target.</param>
|
||||
public void SetSoftTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mouseover target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the focus target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the previous target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the soft target.
|
||||
/// </summary>
|
||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||
public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current target.
|
||||
/// </summary>
|
||||
public void ClearTarget() => this.SetTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the mouseover target.
|
||||
/// </summary>
|
||||
public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the focus target.
|
||||
/// </summary>
|
||||
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previous target.
|
||||
/// </summary>
|
||||
public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the soft target.
|
||||
/// </summary>
|
||||
public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,66 +2,65 @@ using System;
|
|||
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the battle characters.
|
||||
/// </summary>
|
||||
public unsafe class BattleChara : Character
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the battle characters.
|
||||
/// Initializes a new instance of the <see cref="BattleChara"/> class.
|
||||
/// This represents a battle character.
|
||||
/// </summary>
|
||||
public unsafe class BattleChara : Character
|
||||
/// <param name="address">The address of this character in memory.</param>
|
||||
internal BattleChara(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BattleChara"/> class.
|
||||
/// This represents a battle character.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this character in memory.</param>
|
||||
internal BattleChara(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status effects.
|
||||
/// </summary>
|
||||
public StatusList StatusList => new(&this.Struct->StatusManager);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the chara is currently casting.
|
||||
/// </summary>
|
||||
public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the cast is interruptible.
|
||||
/// </summary>
|
||||
public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spell action type of the spell being cast by the actor.
|
||||
/// </summary>
|
||||
public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spell action ID of the spell being cast by the actor.
|
||||
/// </summary>
|
||||
public uint CastActionId => this.Struct->SpellCastInfo.ActionID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of the target currently being cast at by the chara.
|
||||
/// </summary>
|
||||
public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current casting time of the spell being cast by the chara.
|
||||
/// </summary>
|
||||
public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total casting time of the spell being cast by the chara.
|
||||
/// </summary>
|
||||
public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status effects.
|
||||
/// </summary>
|
||||
public StatusList StatusList => new(&this.Struct->StatusManager);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the chara is currently casting.
|
||||
/// </summary>
|
||||
public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the cast is interruptible.
|
||||
/// </summary>
|
||||
public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spell action type of the spell being cast by the actor.
|
||||
/// </summary>
|
||||
public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spell action ID of the spell being cast by the actor.
|
||||
/// </summary>
|
||||
public uint CastActionId => this.Struct->SpellCastInfo.ActionID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of the target currently being cast at by the chara.
|
||||
/// </summary>
|
||||
public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current casting time of the spell being cast by the chara.
|
||||
/// </summary>
|
||||
public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total casting time of the spell being cast by the chara.
|
||||
/// </summary>
|
||||
public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,102 +5,101 @@ using Dalamud.Game.ClientState.Resolvers;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the base for non-static entities.
|
||||
/// </summary>
|
||||
public unsafe class Character : GameObject
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the base for non-static entities.
|
||||
/// Initializes a new instance of the <see cref="Character"/> class.
|
||||
/// This represents a non-static entity.
|
||||
/// </summary>
|
||||
public unsafe class Character : GameObject
|
||||
/// <param name="address">The address of this character in memory.</param>
|
||||
internal Character(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Character"/> class.
|
||||
/// This represents a non-static entity.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this character in memory.</param>
|
||||
internal Character(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current HP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentHp => this.Struct->Health;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum HP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxHp => this.Struct->MaxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current MP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentMp => this.Struct->Mana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum MP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxMp => this.Struct->MaxMana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentGp => this.Struct->GatheringPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum GP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxGp => this.Struct->MaxGatheringPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current CP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentCp => this.Struct->CraftingPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum CP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxCp => this.Struct->MaxCraftingPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClassJob of this Chara.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this Chara.
|
||||
/// </summary>
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a byte array describing the visual appearance of this Chara.
|
||||
/// Indexed by <see cref="CustomizeIndex"/>.
|
||||
/// </summary>
|
||||
public byte[] Customize => MemoryHelper.Read<byte>((IntPtr)this.Struct->CustomizeData, 28);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Free Company tag of this chara.
|
||||
/// </summary>
|
||||
public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target object ID of the character.
|
||||
/// </summary>
|
||||
public override uint TargetObjectId => this.Struct->TargetObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name ID of the character.
|
||||
/// </summary>
|
||||
public uint NameId => this.Struct->NameID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status flags.
|
||||
/// </summary>
|
||||
public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current HP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentHp => this.Struct->Health;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum HP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxHp => this.Struct->MaxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current MP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentMp => this.Struct->Mana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum MP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxMp => this.Struct->MaxMana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentGp => this.Struct->GatheringPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum GP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxGp => this.Struct->MaxGatheringPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current CP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentCp => this.Struct->CraftingPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum CP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxCp => this.Struct->MaxCraftingPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClassJob of this Chara.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this Chara.
|
||||
/// </summary>
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a byte array describing the visual appearance of this Chara.
|
||||
/// Indexed by <see cref="CustomizeIndex"/>.
|
||||
/// </summary>
|
||||
public byte[] Customize => MemoryHelper.Read<byte>((IntPtr)this.Struct->CustomizeData, 28);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Free Company tag of this chara.
|
||||
/// </summary>
|
||||
public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target object ID of the character.
|
||||
/// </summary>
|
||||
public override uint TargetObjectId => this.Struct->TargetObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name ID of the character.
|
||||
/// </summary>
|
||||
public uint NameId => this.Struct->NameID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status flags.
|
||||
/// </summary>
|
||||
public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,171 +5,170 @@ using Dalamud.Game.ClientState.Objects.Enums;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a GameObject in FFXIV.
|
||||
/// </summary>
|
||||
public unsafe partial class GameObject : IEquatable<GameObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a GameObject in FFXIV.
|
||||
/// IDs of non-networked GameObjects.
|
||||
/// </summary>
|
||||
public unsafe partial class GameObject : IEquatable<GameObject>
|
||||
public const uint InvalidGameObjectId = 0xE0000000;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameObject"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this game object in memory.</param>
|
||||
internal GameObject(IntPtr address)
|
||||
{
|
||||
/// <summary>
|
||||
/// IDs of non-networked GameObjects.
|
||||
/// </summary>
|
||||
public const uint InvalidGameObjectId = 0xE0000000;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameObject"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this game object in memory.</param>
|
||||
internal GameObject(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the game object in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud instance.
|
||||
/// </summary>
|
||||
private protected Dalamud Dalamud { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This allows you to <c>if (obj) {...}</c> to check for validity.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The actor to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject);
|
||||
|
||||
public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2)
|
||||
{
|
||||
// Using == results in a stack overflow.
|
||||
if (gameObject1 is null || gameObject2 is null)
|
||||
return Equals(gameObject1, gameObject2);
|
||||
|
||||
return gameObject1.Equals(gameObject2);
|
||||
}
|
||||
|
||||
public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static bool IsValid(GameObject? actor)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (actor is null)
|
||||
return false;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <returns>True or false.</returns>
|
||||
public bool IsValid() => IsValid(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool IEquatable<GameObject>.Equals(GameObject other) => this.ObjectId == other?.ObjectId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj) => ((IEquatable<GameObject>)this).Equals(obj as GameObject);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => this.ObjectId.GetHashCode();
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a basic actor (GameObject) in FFXIV.
|
||||
/// Gets the address of the game object in memory.
|
||||
/// </summary>
|
||||
public unsafe partial class GameObject
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud instance.
|
||||
/// </summary>
|
||||
private protected Dalamud Dalamud { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This allows you to <c>if (obj) {...}</c> to check for validity.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The actor to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject);
|
||||
|
||||
public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64);
|
||||
// Using == results in a stack overflow.
|
||||
if (gameObject1 is null || gameObject2 is null)
|
||||
return Equals(gameObject1, gameObject2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public uint ObjectId => this.Struct->ObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data ID for linking to other respective game data.
|
||||
/// </summary>
|
||||
public uint DataId => this.Struct->DataID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of this GameObject's owner.
|
||||
/// </summary>
|
||||
public uint OwnerId => this.Struct->OwnerID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity kind of this <see cref="GameObject" />.
|
||||
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
|
||||
/// </summary>
|
||||
public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sub kind of this Actor.
|
||||
/// </summary>
|
||||
public byte SubKind => this.Struct->SubKind;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the X distance from the local player in yalms.
|
||||
/// </summary>
|
||||
public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Y distance from the local player in yalms.
|
||||
/// </summary>
|
||||
public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of this <see cref="GameObject" />.
|
||||
/// This ranges from -pi to pi radians.
|
||||
/// </summary>
|
||||
public float Rotation => this.Struct->Rotation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hitbox radius of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public float HitboxRadius => this.Struct->HitboxRadius;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current target of the game object.
|
||||
/// </summary>
|
||||
public virtual uint TargetObjectId => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target object of the game object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
// TODO: Fix for non-networked GameObjects
|
||||
public virtual GameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}";
|
||||
return gameObject1.Equals(gameObject2);
|
||||
}
|
||||
|
||||
public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static bool IsValid(GameObject? actor)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (actor is null)
|
||||
return false;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <returns>True or false.</returns>
|
||||
public bool IsValid() => IsValid(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool IEquatable<GameObject>.Equals(GameObject other) => this.ObjectId == other?.ObjectId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj) => ((IEquatable<GameObject>)this).Equals(obj as GameObject);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => this.ObjectId.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a basic actor (GameObject) in FFXIV.
|
||||
/// </summary>
|
||||
public unsafe partial class GameObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public uint ObjectId => this.Struct->ObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data ID for linking to other respective game data.
|
||||
/// </summary>
|
||||
public uint DataId => this.Struct->DataID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of this GameObject's owner.
|
||||
/// </summary>
|
||||
public uint OwnerId => this.Struct->OwnerID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity kind of this <see cref="GameObject" />.
|
||||
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
|
||||
/// </summary>
|
||||
public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sub kind of this Actor.
|
||||
/// </summary>
|
||||
public byte SubKind => this.Struct->SubKind;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the X distance from the local player in yalms.
|
||||
/// </summary>
|
||||
public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Y distance from the local player in yalms.
|
||||
/// </summary>
|
||||
public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of this <see cref="GameObject" />.
|
||||
/// This ranges from -pi to pi radians.
|
||||
/// </summary>
|
||||
public float Rotation => this.Struct->Rotation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hitbox radius of this <see cref="GameObject" />.
|
||||
/// </summary>
|
||||
public float HitboxRadius => this.Struct->HitboxRadius;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current target of the game object.
|
||||
/// </summary>
|
||||
public virtual uint TargetObjectId => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target object of the game object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
// TODO: Fix for non-networked GameObjects
|
||||
public virtual GameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,178 +7,177 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the actors present in your party or alliance.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed unsafe partial class PartyList
|
||||
{
|
||||
private const int GroupLength = 8;
|
||||
private const int AllianceLength = 20;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the actors present in your party or alliance.
|
||||
/// Initializes a new instance of the <see cref="PartyList"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed unsafe partial class PartyList
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal PartyList(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
private const int GroupLength = 8;
|
||||
private const int AllianceLength = 20;
|
||||
this.address = addressResolver;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal PartyList(ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
|
||||
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of party members the local player has.
|
||||
/// </summary>
|
||||
public int Length => this.GroupManagerStruct->MemberCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the party leader.
|
||||
/// </summary>
|
||||
public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this group is an alliance.
|
||||
/// </summary>
|
||||
public bool IsAlliance => this.GroupManagerStruct->IsAlliance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Group Manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupManagerAddress => this.address.GroupManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the party list within the group manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the alliance member list within the group manager.
|
||||
/// </summary>
|
||||
public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers;
|
||||
|
||||
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Get a party member at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>A <see cref="PartyMember"/> at the specified spawn index.</returns>
|
||||
public PartyMember? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
||||
if (index < 0 || index >= this.Length)
|
||||
return null;
|
||||
|
||||
if (this.Length > GroupLength)
|
||||
{
|
||||
var addr = this.GetAllianceMemberAddress(index);
|
||||
return this.CreateAllianceMemberReference(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
var addr = this.GetPartyMemberAddress(index);
|
||||
return this.CreatePartyMemberReference(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the party member at the specified index of the party list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the party member.</param>
|
||||
/// <returns>The memory address of the party member.</returns>
|
||||
public IntPtr GetPartyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= GroupLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return this.GroupListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV party member.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the party member in memory.</param>
|
||||
/// <returns>The party member object containing the requested data.</returns>
|
||||
public PartyMember? CreatePartyMemberReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new PartyMember(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the alliance member at the specified index of the alliance list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the alliance member.</param>
|
||||
/// <returns>The memory address of the alliance member.</returns>
|
||||
public IntPtr GetAllianceMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= AllianceLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return this.AllianceListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV alliance member.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the alliance member in memory.</param>
|
||||
/// <returns>The party member object containing the requested data.</returns>
|
||||
public PartyMember? CreateAllianceMemberReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new PartyMember(address);
|
||||
}
|
||||
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the party members present in your party or alliance.
|
||||
/// Gets the amount of party members the local player has.
|
||||
/// </summary>
|
||||
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
||||
public int Length => this.GroupManagerStruct->MemberCount;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<PartyMember> GetEnumerator()
|
||||
/// <summary>
|
||||
/// Gets the index of the party leader.
|
||||
/// </summary>
|
||||
public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this group is an alliance.
|
||||
/// </summary>
|
||||
public bool IsAlliance => this.GroupManagerStruct->IsAlliance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Group Manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupManagerAddress => this.address.GroupManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the party list within the group manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the alliance member list within the group manager.
|
||||
/// </summary>
|
||||
public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers;
|
||||
|
||||
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Get a party member at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns>A <see cref="PartyMember"/> at the specified spawn index.</returns>
|
||||
public PartyMember? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
if (index < 0 || index >= this.Length)
|
||||
return null;
|
||||
|
||||
if (this.Length > GroupLength)
|
||||
{
|
||||
var member = this[i];
|
||||
|
||||
if (member == null)
|
||||
break;
|
||||
|
||||
yield return member;
|
||||
var addr = this.GetAllianceMemberAddress(index);
|
||||
return this.CreateAllianceMemberReference(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
var addr = this.GetPartyMemberAddress(index);
|
||||
return this.CreatePartyMemberReference(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
/// <summary>
|
||||
/// Gets the address of the party member at the specified index of the party list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the party member.</param>
|
||||
/// <returns>The memory address of the party member.</returns>
|
||||
public IntPtr GetPartyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= GroupLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return this.GroupListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV party member.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the party member in memory.</param>
|
||||
/// <returns>The party member object containing the requested data.</returns>
|
||||
public PartyMember? CreatePartyMemberReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new PartyMember(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the alliance member at the specified index of the alliance list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the alliance member.</param>
|
||||
/// <returns>The memory address of the alliance member.</returns>
|
||||
public IntPtr GetAllianceMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= AllianceLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return this.AllianceListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV alliance member.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the alliance member in memory.</param>
|
||||
/// <returns>The party member object containing the requested data.</returns>
|
||||
public PartyMember? CreateAllianceMemberReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new PartyMember(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the party members present in your party or alliance.
|
||||
/// </summary>
|
||||
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<PartyMember> GetEnumerator()
|
||||
{
|
||||
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
var member = this[i];
|
||||
|
||||
if (member == null)
|
||||
break;
|
||||
|
||||
yield return member;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,105 +9,104 @@ using Dalamud.Game.Text.SeStringHandling;
|
|||
using Dalamud.Memory;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a party member in the group manager.
|
||||
/// </summary>
|
||||
public unsafe class PartyMember
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a party member in the group manager.
|
||||
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
||||
/// </summary>
|
||||
public unsafe class PartyMember
|
||||
/// <param name="address">Address of the party member.</param>
|
||||
internal PartyMember(IntPtr address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the party member.</param>
|
||||
internal PartyMember(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this party member in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of buffs or debuffs applied to this party member.
|
||||
/// </summary>
|
||||
public StatusList Statuses => new(&this.Struct->StatusManager);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the party member.
|
||||
/// </summary>
|
||||
public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content ID of the party member.
|
||||
/// </summary>
|
||||
public long ContentId => this.Struct->ContentID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor ID of this party member.
|
||||
/// </summary>
|
||||
public uint ObjectId => this.Struct->ObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor associated with this buddy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
public GameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.ObjectId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current HP of this party member.
|
||||
/// </summary>
|
||||
public uint CurrentHP => this.Struct->CurrentHP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum HP of this party member.
|
||||
/// </summary>
|
||||
public uint MaxHP => this.Struct->MaxHP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current MP of this party member.
|
||||
/// </summary>
|
||||
public ushort CurrentMP => this.Struct->CurrentMP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum MP of this party member.
|
||||
/// </summary>
|
||||
public ushort MaxMP => this.Struct->MaxMP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the territory this party member is located in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> Territory => new(this.Struct->TerritoryType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the World this party member resides in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> World => new(this.Struct->HomeWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayname of this party member.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sex of this party member.
|
||||
/// </summary>
|
||||
public byte Sex => this.Struct->Sex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the classjob of this party member.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this party member.
|
||||
/// </summary>
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this party member in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of buffs or debuffs applied to this party member.
|
||||
/// </summary>
|
||||
public StatusList Statuses => new(&this.Struct->StatusManager);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the party member.
|
||||
/// </summary>
|
||||
public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content ID of the party member.
|
||||
/// </summary>
|
||||
public long ContentId => this.Struct->ContentID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor ID of this party member.
|
||||
/// </summary>
|
||||
public uint ObjectId => this.Struct->ObjectID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor associated with this buddy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
public GameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.ObjectId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current HP of this party member.
|
||||
/// </summary>
|
||||
public uint CurrentHP => this.Struct->CurrentHP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum HP of this party member.
|
||||
/// </summary>
|
||||
public uint MaxHP => this.Struct->MaxHP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current MP of this party member.
|
||||
/// </summary>
|
||||
public ushort CurrentMP => this.Struct->CurrentMP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum MP of this party member.
|
||||
/// </summary>
|
||||
public ushort MaxMP => this.Struct->MaxMP;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the territory this party member is located in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> Territory => new(this.Struct->TerritoryType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the World this party member resides in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> World => new(this.Struct->HomeWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayname of this party member.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sex of this party member.
|
||||
/// </summary>
|
||||
public byte Sex => this.Struct->Sex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the classjob of this party member.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this party member.
|
||||
/// </summary>
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
using Dalamud.Data;
|
||||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Resolvers
|
||||
namespace Dalamud.Game.ClientState.Resolvers;
|
||||
|
||||
/// <summary>
|
||||
/// This object resolves a rowID within an Excel sheet.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
|
||||
public class ExcelResolver<T> where T : ExcelRow
|
||||
{
|
||||
/// <summary>
|
||||
/// This object resolves a rowID within an Excel sheet.
|
||||
/// Initializes a new instance of the <see cref="ExcelResolver{T}"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
|
||||
public class ExcelResolver<T> where T : ExcelRow
|
||||
/// <param name="id">The ID of the classJob.</param>
|
||||
internal ExcelResolver(uint id)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExcelResolver{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the classJob.</param>
|
||||
internal ExcelResolver(uint id)
|
||||
{
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID to be resolved.
|
||||
/// </summary>
|
||||
public uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets GameData linked to this excel row.
|
||||
/// </summary>
|
||||
public T GameData => Service<DataManager>.Get().GetExcelSheet<T>().GetRow(this.Id);
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID to be resolved.
|
||||
/// </summary>
|
||||
public uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets GameData linked to this excel row.
|
||||
/// </summary>
|
||||
public T GameData => Service<DataManager>.Get().GetExcelSheet<T>().GetRow(this.Id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,65 +4,64 @@ using Dalamud.Game.ClientState.Objects;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Statuses
|
||||
namespace Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a status effect an actor is afflicted by.
|
||||
/// </summary>
|
||||
public unsafe class Status
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a status effect an actor is afflicted by.
|
||||
/// Initializes a new instance of the <see cref="Status"/> class.
|
||||
/// </summary>
|
||||
public unsafe class Status
|
||||
/// <param name="address">Status address.</param>
|
||||
internal Status(IntPtr address)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Status"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Status address.</param>
|
||||
internal Status(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status ID of this status.
|
||||
/// </summary>
|
||||
public uint StatusId => this.Struct->StatusID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GameData associated with this status.
|
||||
/// </summary>
|
||||
public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver<Lumina.Excel.GeneratedSheets.Status>(this.Struct->StatusID).GameData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter value of the status.
|
||||
/// </summary>
|
||||
public byte Param => this.Struct->Param;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack count of this status.
|
||||
/// </summary>
|
||||
public byte StackCount => this.Struct->StackCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining of this status.
|
||||
/// </summary>
|
||||
public float RemainingTime => this.Struct->RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source ID of this status.
|
||||
/// </summary>
|
||||
public uint SourceID => this.Struct->SourceID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source actor associated with this status.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
public GameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceID);
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status ID of this status.
|
||||
/// </summary>
|
||||
public uint StatusId => this.Struct->StatusID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GameData associated with this status.
|
||||
/// </summary>
|
||||
public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver<Lumina.Excel.GeneratedSheets.Status>(this.Struct->StatusID).GameData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter value of the status.
|
||||
/// </summary>
|
||||
public byte Param => this.Struct->Param;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack count of this status.
|
||||
/// </summary>
|
||||
public byte StackCount => this.Struct->StackCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining of this status.
|
||||
/// </summary>
|
||||
public float RemainingTime => this.Struct->RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source ID of this status.
|
||||
/// </summary>
|
||||
public uint SourceID => this.Struct->SourceID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source actor associated with this status.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
public GameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceID);
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,159 +3,158 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Statuses
|
||||
namespace Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the status effects an actor is afflicted by.
|
||||
/// </summary>
|
||||
public sealed unsafe partial class StatusList
|
||||
{
|
||||
private const int StatusListLength = 30;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the status effects an actor is afflicted by.
|
||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||
/// </summary>
|
||||
public sealed unsafe partial class StatusList
|
||||
/// <param name="address">Address of the status list.</param>
|
||||
internal StatusList(IntPtr address)
|
||||
{
|
||||
private const int StatusListLength = 30;
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the status list.</param>
|
||||
internal StatusList(IntPtr address)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pointer">Pointer to the status list.</param>
|
||||
internal unsafe StatusList(void* pointer)
|
||||
: this((IntPtr)pointer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status list in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of status effect slots the actor has.
|
||||
/// </summary>
|
||||
public int Length => StatusListLength;
|
||||
|
||||
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address;
|
||||
|
||||
/// <summary>
|
||||
/// Get a status effect at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Status Index.</param>
|
||||
/// <returns>The status at the specified index.</returns>
|
||||
public Status? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pointer">Pointer to the status list.</param>
|
||||
internal unsafe StatusList(void* pointer)
|
||||
: this((IntPtr)pointer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status list in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of status effect slots the actor has.
|
||||
/// </summary>
|
||||
public int Length => StatusListLength;
|
||||
|
||||
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address;
|
||||
|
||||
/// <summary>
|
||||
/// Get a status effect at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Status Index.</param>
|
||||
/// <returns>The status at the specified index.</returns>
|
||||
public Status? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index > StatusListLength)
|
||||
return null;
|
||||
|
||||
var addr = this.GetStatusAddress(index);
|
||||
return CreateStatusReference(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV actor status list.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the status list in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static StatusList? CreateStatusListReference(IntPtr address)
|
||||
{
|
||||
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
||||
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
||||
// here or somewhere else.
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
if (index < 0 || index > StatusListLength)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new StatusList(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV actor status.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the status effect in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static Status? CreateStatusReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new Status(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the party member at the specified index of the party list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the party member.</param>
|
||||
/// <returns>The memory address of the party member.</returns>
|
||||
public IntPtr GetStatusAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= StatusListLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (IntPtr)(this.Struct->Status + (index * StatusSize));
|
||||
var addr = this.GetStatusAddress(index);
|
||||
return CreateStatusReference(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the status effects an actor is afflicted by.
|
||||
/// Create a reference to an FFXIV actor status list.
|
||||
/// </summary>
|
||||
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
||||
/// <param name="address">The address of the status list in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static StatusList? CreateStatusListReference(IntPtr address)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<Status>.Count => this.Length;
|
||||
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
||||
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
||||
// here or somewhere else.
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
/// <inheritdoc/>
|
||||
int ICollection.Count => this.Length;
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool ICollection.IsSynchronized => false;
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
object ICollection.SyncRoot => this;
|
||||
return new StatusList(address);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<Status> GetEnumerator()
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV actor status.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the status effect in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static Status? CreateStatusReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new Status(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the party member at the specified index of the party list.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the party member.</param>
|
||||
/// <returns>The memory address of the party member.</returns>
|
||||
public IntPtr GetStatusAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= StatusListLength)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (IntPtr)(this.Struct->Status + (index * StatusSize));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the status effects an actor is afflicted by.
|
||||
/// </summary>
|
||||
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<Status>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
int ICollection.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<Status> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < StatusListLength; i++)
|
||||
{
|
||||
for (var i = 0; i < StatusListLength; i++)
|
||||
{
|
||||
var status = this[i];
|
||||
var status = this[i];
|
||||
|
||||
if (status == null || status.StatusId == 0)
|
||||
continue;
|
||||
if (status == null || status.StatusId == 0)
|
||||
continue;
|
||||
|
||||
yield return status;
|
||||
}
|
||||
yield return status;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
/// <inheritdoc/>
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
array.SetValue(this[i], index);
|
||||
index++;
|
||||
}
|
||||
array.SetValue(this[i], index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,35 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs
|
||||
namespace Dalamud.Game.ClientState.Structs;
|
||||
|
||||
/// <summary>
|
||||
/// Native memory representation of a FFXIV status effect.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// Native memory representation of a FFXIV status effect.
|
||||
/// The effect ID.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The effect ID.
|
||||
/// </summary>
|
||||
public short EffectId;
|
||||
public short EffectId;
|
||||
|
||||
/// <summary>
|
||||
/// How many stacks are present.
|
||||
/// </summary>
|
||||
public byte StackCount;
|
||||
/// <summary>
|
||||
/// How many stacks are present.
|
||||
/// </summary>
|
||||
public byte StackCount;
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters.
|
||||
/// </summary>
|
||||
public byte Param;
|
||||
/// <summary>
|
||||
/// Additional parameters.
|
||||
/// </summary>
|
||||
public byte Param;
|
||||
|
||||
/// <summary>
|
||||
/// The duration remaining.
|
||||
/// </summary>
|
||||
public float Duration;
|
||||
/// <summary>
|
||||
/// The duration remaining.
|
||||
/// </summary>
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the actor that caused this effect.
|
||||
/// </summary>
|
||||
public int OwnerId;
|
||||
}
|
||||
/// <summary>
|
||||
/// The ID of the actor that caused this effect.
|
||||
/// </summary>
|
||||
public int OwnerId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,47 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Dalamud.Game.Command
|
||||
namespace Dalamud.Game.Command;
|
||||
|
||||
/// <summary>
|
||||
/// This class describes a registered command.
|
||||
/// </summary>
|
||||
public sealed class CommandInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// This class describes a registered command.
|
||||
/// Initializes a new instance of the <see cref="CommandInfo"/> class.
|
||||
/// Create a new CommandInfo with the provided handler.
|
||||
/// </summary>
|
||||
public sealed class CommandInfo
|
||||
/// <param name="handler">The method to call when the command is run.</param>
|
||||
public CommandInfo(HandlerDelegate handler)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandInfo"/> class.
|
||||
/// Create a new CommandInfo with the provided handler.
|
||||
/// </summary>
|
||||
/// <param name="handler">The method to call when the command is run.</param>
|
||||
public CommandInfo(HandlerDelegate handler)
|
||||
{
|
||||
this.Handler = handler;
|
||||
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The function to be executed when the command is dispatched.
|
||||
/// </summary>
|
||||
/// <param name="command">The command itself.</param>
|
||||
/// <param name="arguments">The arguments supplied to the command, ready for parsing.</param>
|
||||
public delegate void HandlerDelegate(string command, string arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
|
||||
/// </summary>
|
||||
public HandlerDelegate Handler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the help message for this command.
|
||||
/// </summary>
|
||||
public string HelpMessage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether if this command should be shown in the help output.
|
||||
/// </summary>
|
||||
public bool ShowInHelp { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the assembly responsible for this command.
|
||||
/// </summary>
|
||||
internal string LoaderAssemblyName { get; set; } = string.Empty;
|
||||
this.Handler = handler;
|
||||
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The function to be executed when the command is dispatched.
|
||||
/// </summary>
|
||||
/// <param name="command">The command itself.</param>
|
||||
/// <param name="arguments">The arguments supplied to the command, ready for parsing.</param>
|
||||
public delegate void HandlerDelegate(string command, string arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
|
||||
/// </summary>
|
||||
public HandlerDelegate Handler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the help message for this command.
|
||||
/// </summary>
|
||||
public string HelpMessage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether if this command should be shown in the help output.
|
||||
/// </summary>
|
||||
public bool ShowInHelp { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the assembly responsible for this command.
|
||||
/// </summary>
|
||||
internal string LoaderAssemblyName { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,167 +10,166 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Command
|
||||
namespace Dalamud.Game.Command;
|
||||
|
||||
/// <summary>
|
||||
/// This class manages registered in-game slash commands.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class CommandManager
|
||||
{
|
||||
private readonly Dictionary<string, CommandInfo> commandMap = new();
|
||||
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexFr = new(@"^La commande texte “(?<command>.+)” n'existe pas\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexCn = new(@"^“(?<command>.+)”(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
|
||||
private readonly Regex currentLangCommandRegex;
|
||||
|
||||
/// <summary>
|
||||
/// This class manages registered in-game slash commands.
|
||||
/// Initializes a new instance of the <see cref="CommandManager"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class CommandManager
|
||||
internal CommandManager()
|
||||
{
|
||||
private readonly Dictionary<string, CommandInfo> commandMap = new();
|
||||
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexFr = new(@"^La commande texte “(?<command>.+)” n'existe pas\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexCn = new(@"^“(?<command>.+)”(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
|
||||
private readonly Regex currentLangCommandRegex;
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandManager"/> class.
|
||||
/// </summary>
|
||||
internal CommandManager()
|
||||
this.currentLangCommandRegex = startInfo.Language switch
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
ClientLanguage.Japanese => this.commandRegexJp,
|
||||
ClientLanguage.English => this.commandRegexEn,
|
||||
ClientLanguage.German => this.commandRegexDe,
|
||||
ClientLanguage.French => this.commandRegexFr,
|
||||
_ => this.currentLangCommandRegex,
|
||||
};
|
||||
|
||||
this.currentLangCommandRegex = startInfo.Language switch
|
||||
{
|
||||
ClientLanguage.Japanese => this.commandRegexJp,
|
||||
ClientLanguage.English => this.commandRegexEn,
|
||||
ClientLanguage.German => this.commandRegexDe,
|
||||
ClientLanguage.French => this.commandRegexFr,
|
||||
_ => this.currentLangCommandRegex,
|
||||
};
|
||||
Service<ChatGui>.Get().CheckMessageHandled += this.OnCheckMessageHandled;
|
||||
}
|
||||
|
||||
Service<ChatGui>.Get().CheckMessageHandled += this.OnCheckMessageHandled;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all registered commands.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, CommandInfo> Commands => new(this.commandMap);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all registered commands.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, CommandInfo> Commands => new(this.commandMap);
|
||||
/// <summary>
|
||||
/// Process a command in full.
|
||||
/// </summary>
|
||||
/// <param name="content">The full command string.</param>
|
||||
/// <returns>True if the command was found and dispatched.</returns>
|
||||
public bool ProcessCommand(string content)
|
||||
{
|
||||
string command;
|
||||
string argument;
|
||||
|
||||
/// <summary>
|
||||
/// Process a command in full.
|
||||
/// </summary>
|
||||
/// <param name="content">The full command string.</param>
|
||||
/// <returns>True if the command was found and dispatched.</returns>
|
||||
public bool ProcessCommand(string content)
|
||||
var separatorPosition = content.IndexOf(' ');
|
||||
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length)
|
||||
{
|
||||
string command;
|
||||
string argument;
|
||||
|
||||
var separatorPosition = content.IndexOf(' ');
|
||||
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length)
|
||||
// If no space was found or ends with the space. Process them as a no argument
|
||||
if (separatorPosition + 1 >= content.Length)
|
||||
{
|
||||
// If no space was found or ends with the space. Process them as a no argument
|
||||
if (separatorPosition + 1 >= content.Length)
|
||||
{
|
||||
// Remove the trailing space
|
||||
command = content.Substring(0, separatorPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
command = content;
|
||||
}
|
||||
|
||||
argument = string.Empty;
|
||||
// Remove the trailing space
|
||||
command = content.Substring(0, separatorPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
// e.g.)
|
||||
// /testcommand arg1
|
||||
// => Total of 17 chars
|
||||
// => command: 0-12 (12 chars)
|
||||
// => argument: 13-17 (4 chars)
|
||||
// => content.IndexOf(' ') == 12
|
||||
command = content.Substring(0, separatorPosition);
|
||||
|
||||
var argStart = separatorPosition + 1;
|
||||
argument = content[argStart..];
|
||||
command = content;
|
||||
}
|
||||
|
||||
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
||||
return false;
|
||||
argument = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// e.g.)
|
||||
// /testcommand arg1
|
||||
// => Total of 17 chars
|
||||
// => command: 0-12 (12 chars)
|
||||
// => argument: 13-17 (4 chars)
|
||||
// => content.IndexOf(' ') == 12
|
||||
command = content.Substring(0, separatorPosition);
|
||||
|
||||
this.DispatchCommand(command, argument, handler);
|
||||
var argStart = separatorPosition + 1;
|
||||
argument = content[argStart..];
|
||||
}
|
||||
|
||||
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
||||
return false;
|
||||
|
||||
this.DispatchCommand(command, argument, handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch the handling of a command.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to dispatch.</param>
|
||||
/// <param name="argument">The provided arguments.</param>
|
||||
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
||||
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
info.Handler(command, argument);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a command handler, which you can use to add your own custom commands to the in-game chat.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to register.</param>
|
||||
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
||||
/// <returns>If adding was successful.</returns>
|
||||
public bool AddHandler(string command, CommandInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
||||
|
||||
try
|
||||
{
|
||||
this.commandMap.Add(command, info);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch the handling of a command.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to dispatch.</param>
|
||||
/// <param name="argument">The provided arguments.</param>
|
||||
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
||||
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||
catch (ArgumentException)
|
||||
{
|
||||
try
|
||||
{
|
||||
info.Handler(command, argument);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
|
||||
}
|
||||
Log.Error("Command {CommandName} is already registered.", command);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a command handler, which you can use to add your own custom commands to the in-game chat.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to register.</param>
|
||||
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
||||
/// <returns>If adding was successful.</returns>
|
||||
public bool AddHandler(string command, CommandInfo info)
|
||||
/// <summary>
|
||||
/// Remove a command from the command handlers.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to remove.</param>
|
||||
/// <returns>If the removal was successful.</returns>
|
||||
public bool RemoveHandler(string command)
|
||||
{
|
||||
return this.commandMap.Remove(command);
|
||||
}
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
if (type == XivChatType.ErrorMessage && senderId == 0)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
||||
|
||||
try
|
||||
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
||||
if (cmdMatch.Success)
|
||||
{
|
||||
this.commandMap.Add(command, info);
|
||||
return true;
|
||||
// Yes, it's a chat command.
|
||||
var command = cmdMatch.Value;
|
||||
if (this.ProcessCommand(command)) isHandled = true;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
else
|
||||
{
|
||||
Log.Error("Command {CommandName} is already registered.", command);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a command from the command handlers.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to remove.</param>
|
||||
/// <returns>If the removal was successful.</returns>
|
||||
public bool RemoveHandler(string command)
|
||||
{
|
||||
return this.commandMap.Remove(command);
|
||||
}
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
if (type == XivChatType.ErrorMessage && senderId == 0)
|
||||
{
|
||||
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
||||
// Always match for china, since they patch in language files without changing the ClientLanguage.
|
||||
cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"];
|
||||
if (cmdMatch.Success)
|
||||
{
|
||||
// Yes, it's a chat command.
|
||||
// Yes, it's a Chinese fallback chat command.
|
||||
var command = cmdMatch.Value;
|
||||
if (this.ProcessCommand(command)) isHandled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Always match for china, since they patch in language files without changing the ClientLanguage.
|
||||
cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"];
|
||||
if (cmdMatch.Success)
|
||||
{
|
||||
// Yes, it's a Chinese fallback chat command.
|
||||
var command = cmdMatch.Value;
|
||||
if (this.ProcessCommand(command)) isHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,301 +16,300 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Utility;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class Framework : IDisposable
|
||||
{
|
||||
private static Stopwatch statsStopwatch = new();
|
||||
private Stopwatch updateStopwatch = new();
|
||||
|
||||
private bool tier2Initialized = false;
|
||||
private bool tier3Initialized = false;
|
||||
private bool tierInitError = false;
|
||||
|
||||
private Hook<OnUpdateDetour> updateHook;
|
||||
private Hook<OnDestroyDetour> destroyHook;
|
||||
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
||||
/// Initializes a new instance of the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class Framework : IDisposable
|
||||
internal Framework()
|
||||
{
|
||||
private static Stopwatch statsStopwatch = new();
|
||||
private Stopwatch updateStopwatch = new();
|
||||
this.Address = new FrameworkAddressResolver();
|
||||
this.Address.Setup();
|
||||
|
||||
private bool tier2Initialized = false;
|
||||
private bool tier3Initialized = false;
|
||||
private bool tierInitError = false;
|
||||
|
||||
private Hook<OnUpdateDetour> updateHook;
|
||||
private Hook<OnDestroyDetour> destroyHook;
|
||||
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
internal Framework()
|
||||
Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}");
|
||||
if (this.Address.BaseAddress == IntPtr.Zero)
|
||||
{
|
||||
this.Address = new FrameworkAddressResolver();
|
||||
this.Address.Setup();
|
||||
|
||||
Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}");
|
||||
if (this.Address.BaseAddress == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
||||
}
|
||||
|
||||
// Hook virtual functions
|
||||
this.HookVTable();
|
||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="Update"/> event.
|
||||
/// </summary>
|
||||
/// <param name="framework">The Framework instance.</param>
|
||||
public delegate void OnUpdateDelegate(Framework framework);
|
||||
// Hook virtual functions
|
||||
this.HookVTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used during the native Framework::destroy.
|
||||
/// </summary>
|
||||
/// <param name="framework">The native Framework address.</param>
|
||||
/// <returns>A value indicating if the call was successful.</returns>
|
||||
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="Update"/> event.
|
||||
/// </summary>
|
||||
/// <param name="framework">The Framework instance.</param>
|
||||
public delegate void OnUpdateDelegate(Framework framework);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used during the native Framework::free.
|
||||
/// </summary>
|
||||
/// <returns>The native Framework address.</returns>
|
||||
public delegate IntPtr OnDestroyDelegate();
|
||||
/// <summary>
|
||||
/// A delegate type used during the native Framework::destroy.
|
||||
/// </summary>
|
||||
/// <param name="framework">The native Framework address.</param>
|
||||
/// <returns>A value indicating if the call was successful.</returns>
|
||||
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||
/// <summary>
|
||||
/// A delegate type used during the native Framework::free.
|
||||
/// </summary>
|
||||
/// <returns>The native Framework address.</returns>
|
||||
public delegate IntPtr OnDestroyDelegate();
|
||||
|
||||
private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired every time the game framework updates.
|
||||
/// </summary>
|
||||
public event OnUpdateDelegate Update;
|
||||
private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the collection of stats is enabled.
|
||||
/// </summary>
|
||||
public static bool StatsEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Event that gets fired every time the game framework updates.
|
||||
/// </summary>
|
||||
public event OnUpdateDelegate Update;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stats history mapping.
|
||||
/// </summary>
|
||||
public static Dictionary<string, List<double>> StatsHistory { get; } = new();
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the collection of stats is enabled.
|
||||
/// </summary>
|
||||
public static bool StatsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a raw pointer to the instance of Client::Framework.
|
||||
/// </summary>
|
||||
public FrameworkAddressResolver Address { get; }
|
||||
/// <summary>
|
||||
/// Gets the stats history mapping.
|
||||
/// </summary>
|
||||
public static Dictionary<string, List<double>> StatsHistory { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time that the Framework Update event was triggered.
|
||||
/// </summary>
|
||||
public DateTime LastUpdate { get; private set; } = DateTime.MinValue;
|
||||
/// <summary>
|
||||
/// Gets a raw pointer to the instance of Client::Framework.
|
||||
/// </summary>
|
||||
public FrameworkAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time in UTC that the Framework Update event was triggered.
|
||||
/// </summary>
|
||||
public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue;
|
||||
/// <summary>
|
||||
/// Gets the last time that the Framework Update event was triggered.
|
||||
/// </summary>
|
||||
public DateTime LastUpdate { get; private set; } = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delta between the last Framework Update and the currently executing one.
|
||||
/// </summary>
|
||||
public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero;
|
||||
/// <summary>
|
||||
/// Gets the last time in UTC that the Framework Update event was triggered.
|
||||
/// </summary>
|
||||
public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to dispatch update events.
|
||||
/// </summary>
|
||||
internal bool DispatchUpdateEvents { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Gets the delta between the last Framework Update and the currently executing one.
|
||||
/// </summary>
|
||||
public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Enable this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to dispatch update events.
|
||||
/// </summary>
|
||||
internal bool DispatchUpdateEvents { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
Service<LibcFunction>.Set();
|
||||
Service<GameGui>.Get().Enable();
|
||||
Service<GameNetwork>.Get().Enable();
|
||||
|
||||
this.updateHook.Enable();
|
||||
this.destroyHook.Enable();
|
||||
this.realDestroyHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Service<GameGui>.GetNullable()?.Dispose();
|
||||
Service<GameNetwork>.GetNullable()?.Dispose();
|
||||
|
||||
this.updateHook?.Disable();
|
||||
this.destroyHook?.Disable();
|
||||
this.realDestroyHook?.Disable();
|
||||
Thread.Sleep(500);
|
||||
|
||||
this.updateHook?.Dispose();
|
||||
this.destroyHook?.Dispose();
|
||||
this.realDestroyHook?.Dispose();
|
||||
|
||||
this.updateStopwatch.Reset();
|
||||
statsStopwatch.Reset();
|
||||
}
|
||||
|
||||
private void HookVTable()
|
||||
{
|
||||
var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress);
|
||||
// Virtual function layout:
|
||||
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
||||
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
||||
// .rdata:00000001411F1FF0 dq offset Xiv__Framework__destroy
|
||||
// .rdata:00000001411F1FF8 dq offset Xiv__Framework__free
|
||||
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
||||
|
||||
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
||||
this.updateHook = new Hook<OnUpdateDetour>(pUpdate, this.HandleFrameworkUpdate);
|
||||
|
||||
var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3);
|
||||
this.destroyHook = new Hook<OnDestroyDetour>(pDestroy, this.HandleFrameworkDestroy);
|
||||
|
||||
var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2);
|
||||
this.realDestroyHook = new Hook<OnRealDestroyDelegate>(pRealDestroy, this.HandleRealDestroy);
|
||||
}
|
||||
|
||||
private bool HandleFrameworkUpdate(IntPtr framework)
|
||||
{
|
||||
// If any of the tier loads failed, just go to the original code.
|
||||
if (this.tierInitError)
|
||||
goto original;
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
|
||||
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
||||
if (!this.tier2Initialized)
|
||||
{
|
||||
Service<LibcFunction>.Set();
|
||||
Service<GameGui>.Get().Enable();
|
||||
Service<GameNetwork>.Get().Enable();
|
||||
|
||||
this.updateHook.Enable();
|
||||
this.destroyHook.Enable();
|
||||
this.realDestroyHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Service<GameGui>.GetNullable()?.Dispose();
|
||||
Service<GameNetwork>.GetNullable()?.Dispose();
|
||||
|
||||
this.updateHook?.Disable();
|
||||
this.destroyHook?.Disable();
|
||||
this.realDestroyHook?.Disable();
|
||||
Thread.Sleep(500);
|
||||
|
||||
this.updateHook?.Dispose();
|
||||
this.destroyHook?.Dispose();
|
||||
this.realDestroyHook?.Dispose();
|
||||
|
||||
this.updateStopwatch.Reset();
|
||||
statsStopwatch.Reset();
|
||||
}
|
||||
|
||||
private void HookVTable()
|
||||
{
|
||||
var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress);
|
||||
// Virtual function layout:
|
||||
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
||||
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
||||
// .rdata:00000001411F1FF0 dq offset Xiv__Framework__destroy
|
||||
// .rdata:00000001411F1FF8 dq offset Xiv__Framework__free
|
||||
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
||||
|
||||
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
||||
this.updateHook = new Hook<OnUpdateDetour>(pUpdate, this.HandleFrameworkUpdate);
|
||||
|
||||
var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3);
|
||||
this.destroyHook = new Hook<OnDestroyDetour>(pDestroy, this.HandleFrameworkDestroy);
|
||||
|
||||
var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2);
|
||||
this.realDestroyHook = new Hook<OnRealDestroyDelegate>(pRealDestroy, this.HandleRealDestroy);
|
||||
}
|
||||
|
||||
private bool HandleFrameworkUpdate(IntPtr framework)
|
||||
{
|
||||
// If any of the tier loads failed, just go to the original code.
|
||||
if (this.tierInitError)
|
||||
goto original;
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
|
||||
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
||||
this.tier2Initialized = dalamud.LoadTier2();
|
||||
if (!this.tier2Initialized)
|
||||
{
|
||||
this.tier2Initialized = dalamud.LoadTier2();
|
||||
if (!this.tier2Initialized)
|
||||
this.tierInitError = true;
|
||||
this.tierInitError = true;
|
||||
|
||||
goto original;
|
||||
}
|
||||
goto original;
|
||||
}
|
||||
|
||||
// Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui
|
||||
if (!this.tier3Initialized && Service<InterfaceManager>.GetNullable()?.IsReady == true)
|
||||
{
|
||||
this.tier3Initialized = dalamud.LoadTier3();
|
||||
if (!this.tier3Initialized)
|
||||
this.tierInitError = true;
|
||||
// Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui
|
||||
if (!this.tier3Initialized && Service<InterfaceManager>.GetNullable()?.IsReady == true)
|
||||
{
|
||||
this.tier3Initialized = dalamud.LoadTier3();
|
||||
if (!this.tier3Initialized)
|
||||
this.tierInitError = true;
|
||||
|
||||
goto original;
|
||||
}
|
||||
goto original;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Service<ChatGui>.Get().UpdateQueue();
|
||||
Service<ToastGui>.Get().UpdateQueue();
|
||||
Service<GameNetwork>.Get().UpdateQueue();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
||||
}
|
||||
|
||||
if (this.DispatchUpdateEvents)
|
||||
{
|
||||
this.updateStopwatch.Stop();
|
||||
this.UpdateDelta = TimeSpan.FromMilliseconds(this.updateStopwatch.ElapsedMilliseconds);
|
||||
this.updateStopwatch.Restart();
|
||||
|
||||
this.LastUpdate = DateTime.Now;
|
||||
this.LastUpdateUTC = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
Service<ChatGui>.Get().UpdateQueue();
|
||||
Service<ToastGui>.Get().UpdateQueue();
|
||||
Service<GameNetwork>.Get().UpdateQueue();
|
||||
if (StatsEnabled && this.Update != null)
|
||||
{
|
||||
// Stat Tracking for Framework Updates
|
||||
var invokeList = this.Update.GetInvocationList();
|
||||
var notUpdated = StatsHistory.Keys.ToList();
|
||||
|
||||
// Individually invoke OnUpdate handlers and time them.
|
||||
foreach (var d in invokeList)
|
||||
{
|
||||
statsStopwatch.Restart();
|
||||
d.Method.Invoke(d.Target, new object[] { this });
|
||||
statsStopwatch.Stop();
|
||||
|
||||
var key = $"{d.Target}::{d.Method.Name}";
|
||||
if (notUpdated.Contains(key))
|
||||
notUpdated.Remove(key);
|
||||
|
||||
if (!StatsHistory.ContainsKey(key))
|
||||
StatsHistory.Add(key, new List<double>());
|
||||
|
||||
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
||||
|
||||
if (StatsHistory[key].Count > 1000)
|
||||
{
|
||||
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup handlers that are no longer being called
|
||||
foreach (var key in notUpdated)
|
||||
{
|
||||
if (StatsHistory[key].Count > 0)
|
||||
{
|
||||
StatsHistory[key].RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
StatsHistory.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Update?.Invoke(this);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
||||
}
|
||||
|
||||
if (this.DispatchUpdateEvents)
|
||||
{
|
||||
this.updateStopwatch.Stop();
|
||||
this.UpdateDelta = TimeSpan.FromMilliseconds(this.updateStopwatch.ElapsedMilliseconds);
|
||||
this.updateStopwatch.Restart();
|
||||
|
||||
this.LastUpdate = DateTime.Now;
|
||||
this.LastUpdateUTC = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
if (StatsEnabled && this.Update != null)
|
||||
{
|
||||
// Stat Tracking for Framework Updates
|
||||
var invokeList = this.Update.GetInvocationList();
|
||||
var notUpdated = StatsHistory.Keys.ToList();
|
||||
|
||||
// Individually invoke OnUpdate handlers and time them.
|
||||
foreach (var d in invokeList)
|
||||
{
|
||||
statsStopwatch.Restart();
|
||||
d.Method.Invoke(d.Target, new object[] { this });
|
||||
statsStopwatch.Stop();
|
||||
|
||||
var key = $"{d.Target}::{d.Method.Name}";
|
||||
if (notUpdated.Contains(key))
|
||||
notUpdated.Remove(key);
|
||||
|
||||
if (!StatsHistory.ContainsKey(key))
|
||||
StatsHistory.Add(key, new List<double>());
|
||||
|
||||
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
||||
|
||||
if (StatsHistory[key].Count > 1000)
|
||||
{
|
||||
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup handlers that are no longer being called
|
||||
foreach (var key in notUpdated)
|
||||
{
|
||||
if (StatsHistory[key].Count > 0)
|
||||
{
|
||||
StatsHistory[key].RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
StatsHistory.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Update?.Invoke(this);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
||||
}
|
||||
}
|
||||
|
||||
original:
|
||||
return this.updateHook.Original(framework);
|
||||
}
|
||||
|
||||
private bool HandleRealDestroy(IntPtr framework)
|
||||
original:
|
||||
return this.updateHook.Original(framework);
|
||||
}
|
||||
|
||||
private bool HandleRealDestroy(IntPtr framework)
|
||||
{
|
||||
if (this.DispatchUpdateEvents)
|
||||
{
|
||||
if (this.DispatchUpdateEvents)
|
||||
{
|
||||
Log.Information("Framework::Destroy!");
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
dalamud.DisposePlugins();
|
||||
|
||||
Log.Information("Framework::Destroy OK!");
|
||||
}
|
||||
|
||||
this.DispatchUpdateEvents = false;
|
||||
|
||||
return this.realDestroyHook.Original(framework);
|
||||
}
|
||||
|
||||
private IntPtr HandleFrameworkDestroy()
|
||||
{
|
||||
Log.Information("Framework::Free!");
|
||||
|
||||
// Store the pointer to the original trampoline location
|
||||
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original);
|
||||
Log.Information("Framework::Destroy!");
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
dalamud.Unload();
|
||||
dalamud.WaitForUnloadFinish();
|
||||
dalamud.DisposePlugins();
|
||||
|
||||
Log.Information("Framework::Free OK!");
|
||||
|
||||
// Return the original trampoline location to cleanly exit
|
||||
return originalPtr;
|
||||
Log.Information("Framework::Destroy OK!");
|
||||
}
|
||||
|
||||
this.DispatchUpdateEvents = false;
|
||||
|
||||
return this.realDestroyHook.Original(framework);
|
||||
}
|
||||
|
||||
private IntPtr HandleFrameworkDestroy()
|
||||
{
|
||||
Log.Information("Framework::Free!");
|
||||
|
||||
// Store the pointer to the original trampoline location
|
||||
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original);
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
dalamud.Unload();
|
||||
dalamud.WaitForUnloadFinish();
|
||||
|
||||
Log.Information("Framework::Free OK!");
|
||||
|
||||
// Return the original trampoline location to cleanly exit
|
||||
return originalPtr;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,54 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Internal;
|
||||
namespace Dalamud.Game;
|
||||
|
||||
namespace Dalamud.Game
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
public sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="Framework"/> class.
|
||||
/// Gets the base address native Framework class.
|
||||
/// </summary>
|
||||
public sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native GuiManager class.
|
||||
/// </summary>
|
||||
public IntPtr GuiManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native ScriptManager class.
|
||||
/// </summary>
|
||||
public IntPtr ScriptManager { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base address native Framework class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
this.SetupFramework(sig);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native GuiManager class.
|
||||
/// </summary>
|
||||
public IntPtr GuiManager { get; private set; }
|
||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
||||
this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native ScriptManager class.
|
||||
/// </summary>
|
||||
public IntPtr ScriptManager { get; private set; }
|
||||
// Called from Framework::Init
|
||||
this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.SetupFramework(sig);
|
||||
private void SetupFramework(SigScanner scanner)
|
||||
{
|
||||
// Dissasembly of part of the .dtor
|
||||
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
||||
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
|
||||
// 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38]
|
||||
// 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0
|
||||
// 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80
|
||||
var fwDtor = scanner.ScanText("48 C7 05 ?? ?? ?? ?? 00 00 00 00 E8 ?? ?? ?? ?? 48 8D ?? ?? ?? 00 00 E8 ?? ?? ?? ?? 48 8D");
|
||||
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
|
||||
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
||||
|
||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
||||
this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08);
|
||||
|
||||
// Called from Framework::Init
|
||||
this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here
|
||||
}
|
||||
|
||||
private void SetupFramework(SigScanner scanner)
|
||||
{
|
||||
// Dissasembly of part of the .dtor
|
||||
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
||||
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
|
||||
// 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38]
|
||||
// 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0
|
||||
// 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80
|
||||
var fwDtor = scanner.ScanText("48 C7 05 ?? ?? ?? ?? 00 00 00 00 E8 ?? ?? ?? ?? 48 8D ?? ?? ?? 00 00 E8 ?? ?? ?? ?? 48 8D");
|
||||
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
|
||||
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
||||
|
||||
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
||||
this.BaseAddress = Marshal.ReadIntPtr(pFramework);
|
||||
}
|
||||
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
||||
this.BaseAddress = Marshal.ReadIntPtr(pFramework);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,405 +5,404 @@ using System.Text;
|
|||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// A GameVersion object contains give hierarchical numeric components: year, month,
|
||||
/// day, major and minor. All components may be unspecified, which is represented
|
||||
/// internally as a -1. By definition, an unspecified component matches anything
|
||||
/// (both unspecified and specified), and an unspecified component is "less than" any
|
||||
/// specified component. It will also equal the string "any" if all components are
|
||||
/// unspecified. The value can be retrieved from the ffxivgame.ver file in your game
|
||||
/// installation directory.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersion>, IEquatable<GameVersion>
|
||||
{
|
||||
private static readonly GameVersion AnyVersion = new();
|
||||
|
||||
/// <summary>
|
||||
/// A GameVersion object contains give hierarchical numeric components: year, month,
|
||||
/// day, major and minor. All components may be unspecified, which is represented
|
||||
/// internally as a -1. By definition, an unspecified component matches anything
|
||||
/// (both unspecified and specified), and an unspecified component is "less than" any
|
||||
/// specified component. It will also equal the string "any" if all components are
|
||||
/// unspecified. The value can be retrieved from the ffxivgame.ver file in your game
|
||||
/// installation directory.
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersion>, IEquatable<GameVersion>
|
||||
/// <param name="version">Version string to parse.</param>
|
||||
[JsonConstructor]
|
||||
public GameVersion(string version)
|
||||
{
|
||||
private static readonly GameVersion AnyVersion = new();
|
||||
var ver = Parse(version);
|
||||
this.Year = ver.Year;
|
||||
this.Month = ver.Month;
|
||||
this.Day = ver.Day;
|
||||
this.Major = ver.Major;
|
||||
this.Minor = ver.Minor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="version">Version string to parse.</param>
|
||||
[JsonConstructor]
|
||||
public GameVersion(string version)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
/// <param name="day">The day.</param>
|
||||
/// <param name="major">The major version.</param>
|
||||
/// <param name="minor">The minor version.</param>
|
||||
public GameVersion(int year, int month, int day, int major, int minor)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
|
||||
if ((this.Day = day) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(day));
|
||||
|
||||
if ((this.Major = major) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(major));
|
||||
|
||||
if ((this.Minor = minor) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(minor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
/// <param name="day">The day.</param>
|
||||
/// <param name="major">The major version.</param>
|
||||
public GameVersion(int year, int month, int day, int major)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
|
||||
if ((this.Day = day) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(day));
|
||||
|
||||
if ((this.Major = major) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(major));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
/// <param name="day">The day.</param>
|
||||
public GameVersion(int year, int month, int day)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
|
||||
if ((this.Day = day) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(day));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
public GameVersion(int year, int month)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
public GameVersion(int year)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
public GameVersion()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default "any" game version.
|
||||
/// </summary>
|
||||
public static GameVersion Any => AnyVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the year component.
|
||||
/// </summary>
|
||||
public int Year { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the month component.
|
||||
/// </summary>
|
||||
public int Month { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the day component.
|
||||
/// </summary>
|
||||
public int Day { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the major version component.
|
||||
/// </summary>
|
||||
public int Major { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minor version component.
|
||||
/// </summary>
|
||||
public int Minor { get; } = -1;
|
||||
|
||||
public static implicit operator GameVersion(string ver)
|
||||
{
|
||||
return Parse(ver);
|
||||
}
|
||||
|
||||
public static bool operator ==(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
if (v1 is null)
|
||||
{
|
||||
var ver = Parse(version);
|
||||
this.Year = ver.Year;
|
||||
this.Month = ver.Month;
|
||||
this.Day = ver.Day;
|
||||
this.Major = ver.Major;
|
||||
this.Minor = ver.Minor;
|
||||
return v2 is null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
/// <param name="day">The day.</param>
|
||||
/// <param name="major">The major version.</param>
|
||||
/// <param name="minor">The minor version.</param>
|
||||
public GameVersion(int year, int month, int day, int major, int minor)
|
||||
return v1.Equals(v2);
|
||||
}
|
||||
|
||||
public static bool operator !=(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
return !(v1 == v2);
|
||||
}
|
||||
|
||||
public static bool operator <(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
if (v1 is null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
return v1.CompareTo(v2) < 0;
|
||||
}
|
||||
|
||||
public static bool operator <=(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
if (v1 is null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
return v1.CompareTo(v2) <= 0;
|
||||
}
|
||||
|
||||
public static bool operator >(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
return v2 < v1;
|
||||
}
|
||||
|
||||
public static bool operator >=(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
return v2 <= v1;
|
||||
}
|
||||
|
||||
public static GameVersion operator +(GameVersion v1, TimeSpan v2)
|
||||
{
|
||||
if (v1 == null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
||||
return v1;
|
||||
|
||||
var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2;
|
||||
|
||||
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
||||
}
|
||||
|
||||
public static GameVersion operator -(GameVersion v1, TimeSpan v2)
|
||||
{
|
||||
if (v1 == null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
||||
return v1;
|
||||
|
||||
var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2;
|
||||
|
||||
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a version string. YYYY.MM.DD.majr.minr or "any".
|
||||
/// </summary>
|
||||
/// <param name="input">Input to parse.</param>
|
||||
/// <returns>GameVersion object.</returns>
|
||||
public static GameVersion Parse(string input)
|
||||
{
|
||||
if (input == null)
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
if (input.ToLower(CultureInfo.InvariantCulture) == "any")
|
||||
return new GameVersion();
|
||||
|
||||
var parts = input.Split('.');
|
||||
var tplParts = parts.Select(p =>
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
var result = int.TryParse(p, out var value);
|
||||
return (result, value);
|
||||
}).ToArray();
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
if (tplParts.Any(t => !t.result))
|
||||
throw new FormatException("Bad formatting");
|
||||
|
||||
if ((this.Day = day) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(day));
|
||||
var intParts = tplParts.Select(t => t.value).ToArray();
|
||||
var len = intParts.Length;
|
||||
|
||||
if ((this.Major = major) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(major));
|
||||
if (len == 1)
|
||||
return new GameVersion(intParts[0]);
|
||||
else if (len == 2)
|
||||
return new GameVersion(intParts[0], intParts[1]);
|
||||
else if (len == 3)
|
||||
return new GameVersion(intParts[0], intParts[1], intParts[2]);
|
||||
else if (len == 4)
|
||||
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]);
|
||||
else if (len == 5)
|
||||
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]);
|
||||
else
|
||||
throw new ArgumentException("Too many parts");
|
||||
}
|
||||
|
||||
if ((this.Minor = minor) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(minor));
|
||||
/// <summary>
|
||||
/// Try to parse a version string. YYYY.MM.DD.majr.minr or "any".
|
||||
/// </summary>
|
||||
/// <param name="input">Input to parse.</param>
|
||||
/// <param name="result">GameVersion object.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public static bool TryParse(string input, out GameVersion result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Parse(input);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
/// <param name="day">The day.</param>
|
||||
/// <param name="major">The major version.</param>
|
||||
public GameVersion(int year, int month, int day, int major)
|
||||
catch
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
|
||||
if ((this.Day = day) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(day));
|
||||
|
||||
if ((this.Major = major) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(major));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
/// <param name="day">The day.</param>
|
||||
public GameVersion(int year, int month, int day)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
|
||||
if ((this.Day = day) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(day));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
/// <param name="month">The month.</param>
|
||||
public GameVersion(int year, int month)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
|
||||
if ((this.Month = month) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(month));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year.</param>
|
||||
public GameVersion(int year)
|
||||
{
|
||||
if ((this.Year = year) < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(year));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||
/// </summary>
|
||||
public GameVersion()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default "any" game version.
|
||||
/// </summary>
|
||||
public static GameVersion Any => AnyVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the year component.
|
||||
/// </summary>
|
||||
public int Year { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the month component.
|
||||
/// </summary>
|
||||
public int Month { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the day component.
|
||||
/// </summary>
|
||||
public int Day { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the major version component.
|
||||
/// </summary>
|
||||
public int Major { get; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minor version component.
|
||||
/// </summary>
|
||||
public int Minor { get; } = -1;
|
||||
|
||||
public static implicit operator GameVersion(string ver)
|
||||
{
|
||||
return Parse(ver);
|
||||
}
|
||||
|
||||
public static bool operator ==(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
if (v1 is null)
|
||||
{
|
||||
return v2 is null;
|
||||
}
|
||||
|
||||
return v1.Equals(v2);
|
||||
}
|
||||
|
||||
public static bool operator !=(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
return !(v1 == v2);
|
||||
}
|
||||
|
||||
public static bool operator <(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
if (v1 is null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
return v1.CompareTo(v2) < 0;
|
||||
}
|
||||
|
||||
public static bool operator <=(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
if (v1 is null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
return v1.CompareTo(v2) <= 0;
|
||||
}
|
||||
|
||||
public static bool operator >(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
return v2 < v1;
|
||||
}
|
||||
|
||||
public static bool operator >=(GameVersion v1, GameVersion v2)
|
||||
{
|
||||
return v2 <= v1;
|
||||
}
|
||||
|
||||
public static GameVersion operator +(GameVersion v1, TimeSpan v2)
|
||||
{
|
||||
if (v1 == null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
||||
return v1;
|
||||
|
||||
var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2;
|
||||
|
||||
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
||||
}
|
||||
|
||||
public static GameVersion operator -(GameVersion v1, TimeSpan v2)
|
||||
{
|
||||
if (v1 == null)
|
||||
throw new ArgumentNullException(nameof(v1));
|
||||
|
||||
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
||||
return v1;
|
||||
|
||||
var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2;
|
||||
|
||||
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a version string. YYYY.MM.DD.majr.minr or "any".
|
||||
/// </summary>
|
||||
/// <param name="input">Input to parse.</param>
|
||||
/// <returns>GameVersion object.</returns>
|
||||
public static GameVersion Parse(string input)
|
||||
{
|
||||
if (input == null)
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
if (input.ToLower(CultureInfo.InvariantCulture) == "any")
|
||||
return new GameVersion();
|
||||
|
||||
var parts = input.Split('.');
|
||||
var tplParts = parts.Select(p =>
|
||||
{
|
||||
var result = int.TryParse(p, out var value);
|
||||
return (result, value);
|
||||
}).ToArray();
|
||||
|
||||
if (tplParts.Any(t => !t.result))
|
||||
throw new FormatException("Bad formatting");
|
||||
|
||||
var intParts = tplParts.Select(t => t.value).ToArray();
|
||||
var len = intParts.Length;
|
||||
|
||||
if (len == 1)
|
||||
return new GameVersion(intParts[0]);
|
||||
else if (len == 2)
|
||||
return new GameVersion(intParts[0], intParts[1]);
|
||||
else if (len == 3)
|
||||
return new GameVersion(intParts[0], intParts[1], intParts[2]);
|
||||
else if (len == 4)
|
||||
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]);
|
||||
else if (len == 5)
|
||||
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]);
|
||||
else
|
||||
throw new ArgumentException("Too many parts");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to parse a version string. YYYY.MM.DD.majr.minr or "any".
|
||||
/// </summary>
|
||||
/// <param name="input">Input to parse.</param>
|
||||
/// <param name="result">GameVersion object.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public static bool TryParse(string input, out GameVersion result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Parse(input);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return 1;
|
||||
|
||||
if (obj is GameVersion value)
|
||||
{
|
||||
return this.CompareTo(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Argument must be a GameVersion");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CompareTo(GameVersion value)
|
||||
{
|
||||
if (value == null)
|
||||
return 1;
|
||||
|
||||
if (this == value)
|
||||
return 0;
|
||||
|
||||
if (this == AnyVersion)
|
||||
return 1;
|
||||
|
||||
if (value == AnyVersion)
|
||||
return -1;
|
||||
|
||||
if (this.Year != value.Year)
|
||||
return this.Year > value.Year ? 1 : -1;
|
||||
|
||||
if (this.Month != value.Month)
|
||||
return this.Month > value.Month ? 1 : -1;
|
||||
|
||||
if (this.Day != value.Day)
|
||||
return this.Day > value.Day ? 1 : -1;
|
||||
|
||||
if (this.Major != value.Major)
|
||||
return this.Major > value.Major ? 1 : -1;
|
||||
|
||||
if (this.Minor != value.Minor)
|
||||
return this.Minor > value.Minor ? 1 : -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is not GameVersion value)
|
||||
return false;
|
||||
|
||||
return this.Equals(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(GameVersion value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
(this.Year == value.Year) &&
|
||||
(this.Month == value.Month) &&
|
||||
(this.Day == value.Day) &&
|
||||
(this.Major == value.Major) &&
|
||||
(this.Minor == value.Minor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var accumulator = 0;
|
||||
|
||||
// This might be horribly wrong, but it isn't used heavily.
|
||||
accumulator |= this.Year.GetHashCode();
|
||||
accumulator |= this.Month.GetHashCode();
|
||||
accumulator |= this.Day.GetHashCode();
|
||||
accumulator |= this.Major.GetHashCode();
|
||||
accumulator |= this.Minor.GetHashCode();
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.Year == -1 &&
|
||||
this.Month == -1 &&
|
||||
this.Day == -1 &&
|
||||
this.Major == -1 &&
|
||||
this.Minor == -1)
|
||||
return "any";
|
||||
|
||||
return new StringBuilder()
|
||||
.Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year))
|
||||
.Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month))
|
||||
.Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day))
|
||||
.Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major))
|
||||
.Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor))
|
||||
.ToString();
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return 1;
|
||||
|
||||
if (obj is GameVersion value)
|
||||
{
|
||||
return this.CompareTo(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Argument must be a GameVersion");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CompareTo(GameVersion value)
|
||||
{
|
||||
if (value == null)
|
||||
return 1;
|
||||
|
||||
if (this == value)
|
||||
return 0;
|
||||
|
||||
if (this == AnyVersion)
|
||||
return 1;
|
||||
|
||||
if (value == AnyVersion)
|
||||
return -1;
|
||||
|
||||
if (this.Year != value.Year)
|
||||
return this.Year > value.Year ? 1 : -1;
|
||||
|
||||
if (this.Month != value.Month)
|
||||
return this.Month > value.Month ? 1 : -1;
|
||||
|
||||
if (this.Day != value.Day)
|
||||
return this.Day > value.Day ? 1 : -1;
|
||||
|
||||
if (this.Major != value.Major)
|
||||
return this.Major > value.Major ? 1 : -1;
|
||||
|
||||
if (this.Minor != value.Minor)
|
||||
return this.Minor > value.Minor ? 1 : -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is not GameVersion value)
|
||||
return false;
|
||||
|
||||
return this.Equals(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(GameVersion value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
(this.Year == value.Year) &&
|
||||
(this.Month == value.Month) &&
|
||||
(this.Day == value.Day) &&
|
||||
(this.Major == value.Major) &&
|
||||
(this.Minor == value.Minor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var accumulator = 0;
|
||||
|
||||
// This might be horribly wrong, but it isn't used heavily.
|
||||
accumulator |= this.Year.GetHashCode();
|
||||
accumulator |= this.Month.GetHashCode();
|
||||
accumulator |= this.Day.GetHashCode();
|
||||
accumulator |= this.Major.GetHashCode();
|
||||
accumulator |= this.Minor.GetHashCode();
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.Year == -1 &&
|
||||
this.Month == -1 &&
|
||||
this.Day == -1 &&
|
||||
this.Major == -1 &&
|
||||
this.Minor == -1)
|
||||
return "any";
|
||||
|
||||
return new StringBuilder()
|
||||
.Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year))
|
||||
.Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month))
|
||||
.Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day))
|
||||
.Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major))
|
||||
.Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor))
|
||||
.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,79 +2,78 @@ using System;
|
|||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
|
||||
/// </summary>
|
||||
public sealed class GameVersionConverter : JsonConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
|
||||
/// Writes the JSON representation of the object.
|
||||
/// </summary>
|
||||
public sealed class GameVersionConverter : JsonConverter
|
||||
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
if (value == null)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
else if (value is GameVersion)
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException("Expected GameVersion object value");
|
||||
}
|
||||
writer.WriteNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
/// <returns>The object value.</returns>
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
else if (value is GameVersion)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new GameVersion((string)reader.Value!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}");
|
||||
}
|
||||
}
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance can convert the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool CanConvert(Type objectType)
|
||||
else
|
||||
{
|
||||
return objectType == typeof(GameVersion);
|
||||
throw new JsonSerializationException("Expected GameVersion object value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
/// <returns>The object value.</returns>
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new GameVersion((string)reader.Value!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance can convert the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(GameVersion);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,471 +13,470 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native chat UI.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class ChatGui : IDisposable
|
||||
{
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native chat UI.
|
||||
/// Initializes a new instance of the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class ChatGui : IDisposable
|
||||
/// <param name="baseAddress">The base address of the ChatManager.</param>
|
||||
internal ChatGui(IntPtr baseAddress)
|
||||
{
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
this.address = new ChatGuiAddressResolver(baseAddress);
|
||||
this.address.Setup();
|
||||
|
||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||
Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}");
|
||||
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = new Hook<InteractableLinkClickedDelegate>(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
}
|
||||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">The base address of the ChatManager.</param>
|
||||
internal ChatGui(IntPtr baseAddress)
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||
/// </summary>
|
||||
public event OnMessageDelegate ChatMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
||||
/// </summary>
|
||||
public event OnCheckMessageHandledDelegate CheckMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageHandledDelegate ChatMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the last linked item.
|
||||
/// </summary>
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags of the last linked item.
|
||||
/// </summary>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="chat">A message to send.</param>
|
||||
public void PrintChat(XivChatEntry chat)
|
||||
{
|
||||
this.chatQueue.Enqueue(chat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(string message)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
this.address = new ChatGuiAddressResolver(baseAddress);
|
||||
this.address.Setup();
|
||||
Message = message,
|
||||
Type = configuration.GeneralChatType,
|
||||
});
|
||||
}
|
||||
|
||||
Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}");
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(SeString message)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = new Hook<InteractableLinkClickedDelegate>(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||
/// </summary>
|
||||
public event OnMessageDelegate ChatMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
||||
/// </summary>
|
||||
public event OnCheckMessageHandledDelegate CheckMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageHandledDelegate ChatMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the last linked item.
|
||||
/// </summary>
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags of the last linked item.
|
||||
/// </summary>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
}
|
||||
Message = message,
|
||||
Type = configuration.GeneralChatType,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(string message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
}
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="chat">A message to send.</param>
|
||||
public void PrintChat(XivChatEntry chat)
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(SeString message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
this.chatQueue.Enqueue(chat);
|
||||
}
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(string message)
|
||||
/// <summary>
|
||||
/// Process a chat queue.
|
||||
/// </summary>
|
||||
public void UpdateQueue()
|
||||
{
|
||||
while (this.chatQueue.Count > 0)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var chat = this.chatQueue.Dequeue();
|
||||
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
{
|
||||
Message = message,
|
||||
Type = configuration.GeneralChatType,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(SeString message)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = configuration.GeneralChatType,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(string message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(SeString message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a chat queue.
|
||||
/// </summary>
|
||||
public void UpdateQueue()
|
||||
{
|
||||
while (this.chatQueue.Count > 0)
|
||||
{
|
||||
var chat = this.chatQueue.Dequeue();
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
||||
using var senderOwned = Service<LibcFunction>.Get().NewString(senderRaw);
|
||||
|
||||
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
||||
using var messageOwned = Service<LibcFunction>.Get().NewString(messageRaw);
|
||||
|
||||
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to run.</param>
|
||||
/// <param name="commandAction">The command action itself.</param>
|
||||
/// <returns>A payload for handling.</returns>
|
||||
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
|
||||
{
|
||||
var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
|
||||
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
||||
return payload;
|
||||
}
|
||||
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
||||
using var senderOwned = Service<LibcFunction>.Get().NewString(senderRaw);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all handlers owned by a plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the links.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName)
|
||||
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
||||
using var messageOwned = Service<LibcFunction>.Get().NewString(messageRaw);
|
||||
|
||||
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to run.</param>
|
||||
/// <param name="commandAction">The command action itself.</param>
|
||||
/// <returns>A payload for handling.</returns>
|
||||
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
|
||||
{
|
||||
var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
|
||||
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all handlers owned by a plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the links.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName)
|
||||
{
|
||||
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName))
|
||||
{
|
||||
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName))
|
||||
this.dalamudLinkHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a registered link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to be removed.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
|
||||
{
|
||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2)
|
||||
{
|
||||
// Copyright (c) 2008-2013 Hafthor Stefansson
|
||||
// Distributed under the MIT/X11 software license
|
||||
// Ref: http://www.opensource.org/licenses/mit-license.php.
|
||||
// https://stackoverflow.com/a/8808245
|
||||
|
||||
if (a1 == a2) return true;
|
||||
if (a1 == null || a2 == null || a1.Length != a2.Length)
|
||||
return false;
|
||||
fixed (byte* p1 = a1, p2 = a2)
|
||||
{
|
||||
byte* x1 = p1, x2 = p2;
|
||||
var l = a1.Length;
|
||||
for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
|
||||
{
|
||||
this.dalamudLinkHandlers.Remove(handler);
|
||||
if (*((long*)x1) != *((long*)x2))
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((l & 4) != 0)
|
||||
{
|
||||
if (*((int*)x1) != *((int*)x2))
|
||||
return false;
|
||||
x1 += 4;
|
||||
x2 += 4;
|
||||
}
|
||||
|
||||
if ((l & 2) != 0)
|
||||
{
|
||||
if (*((short*)x1) != *((short*)x2))
|
||||
return false;
|
||||
x1 += 2;
|
||||
x2 += 2;
|
||||
}
|
||||
|
||||
if ((l & 1) != 0)
|
||||
{
|
||||
if (*((byte*)x1) != *((byte*)x2))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
|
||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
var sender = StdString.ReadFromPointer(pSenderName);
|
||||
var parsedSender = SeString.Parse(sender.RawData);
|
||||
var originalSenderData = (byte[])sender.RawData.Clone();
|
||||
var oldEditedSender = parsedSender.Encode();
|
||||
var senderPtr = pSenderName;
|
||||
OwnedStdString allocatedString = null;
|
||||
|
||||
var message = StdString.ReadFromPointer(pMessage);
|
||||
var parsedMessage = SeString.Parse(message.RawData);
|
||||
var originalMessageData = (byte[])message.RawData.Clone();
|
||||
var oldEdited = parsedMessage.Encode();
|
||||
var messagePtr = pMessage;
|
||||
OwnedStdString allocatedStringSender = null;
|
||||
|
||||
// Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
||||
|
||||
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
this.CheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
|
||||
if (!isHandled)
|
||||
{
|
||||
this.ChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
|
||||
var newEdited = parsedMessage.Encode();
|
||||
if (!FastByteArrayCompare(oldEdited, newEdited))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
message.RawData = newEdited;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
|
||||
if (!FastByteArrayCompare(originalMessageData, message.RawData))
|
||||
{
|
||||
allocatedString = Service<LibcFunction>.Get().NewString(message.RawData);
|
||||
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||
messagePtr = allocatedString.Address;
|
||||
}
|
||||
|
||||
var newEditedSender = parsedSender.Encode();
|
||||
if (!FastByteArrayCompare(oldEditedSender, newEditedSender))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
sender.RawData = newEditedSender;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
|
||||
if (!FastByteArrayCompare(originalSenderData, sender.RawData))
|
||||
{
|
||||
allocatedStringSender = Service<LibcFunction>.Get().NewString(sender.RawData);
|
||||
Log.Debug(
|
||||
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
||||
senderPtr = allocatedStringSender.Address;
|
||||
}
|
||||
|
||||
// Print the original chat if it's handled.
|
||||
if (isHandled)
|
||||
{
|
||||
this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter);
|
||||
this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
}
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
this.baseAddress = manager;
|
||||
|
||||
allocatedString?.Dispose();
|
||||
allocatedStringSender?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a registered link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to be removed.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2)
|
||||
{
|
||||
// Copyright (c) 2008-2013 Hafthor Stefansson
|
||||
// Distributed under the MIT/X11 software license
|
||||
// Ref: http://www.opensource.org/licenses/mit-license.php.
|
||||
// https://stackoverflow.com/a/8808245
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
if (a1 == a2) return true;
|
||||
if (a1 == null || a2 == null || a1.Length != a2.Length)
|
||||
return false;
|
||||
fixed (byte* p1 = a1, p2 = a2)
|
||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||
var messageSize = 0;
|
||||
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
|
||||
var payloadBytes = new byte[messageSize];
|
||||
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
|
||||
var seStr = SeString.Parse(payloadBytes);
|
||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
{
|
||||
byte* x1 = p1, x2 = p2;
|
||||
var l = a1.Length;
|
||||
for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||
{
|
||||
if (*((long*)x1) != *((long*)x2))
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((l & 4) != 0)
|
||||
{
|
||||
if (*((int*)x1) != *((int*)x2))
|
||||
return false;
|
||||
x1 += 4;
|
||||
x2 += 4;
|
||||
}
|
||||
|
||||
if ((l & 2) != 0)
|
||||
{
|
||||
if (*((short*)x1) != *((short*)x2))
|
||||
return false;
|
||||
x1 += 2;
|
||||
x2 += 2;
|
||||
}
|
||||
|
||||
if ((l & 1) != 0)
|
||||
{
|
||||
if (*((byte*)x1) != *((byte*)x2))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
|
||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
var sender = StdString.ReadFromPointer(pSenderName);
|
||||
var parsedSender = SeString.Parse(sender.RawData);
|
||||
var originalSenderData = (byte[])sender.RawData.Clone();
|
||||
var oldEditedSender = parsedSender.Encode();
|
||||
var senderPtr = pSenderName;
|
||||
OwnedStdString allocatedString = null;
|
||||
|
||||
var message = StdString.ReadFromPointer(pMessage);
|
||||
var parsedMessage = SeString.Parse(message.RawData);
|
||||
var originalMessageData = (byte[])message.RawData.Clone();
|
||||
var oldEdited = parsedMessage.Encode();
|
||||
var messagePtr = pMessage;
|
||||
OwnedStdString allocatedStringSender = null;
|
||||
|
||||
// Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
||||
|
||||
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
this.CheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
|
||||
if (!isHandled)
|
||||
{
|
||||
this.ChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
|
||||
var newEdited = parsedMessage.Encode();
|
||||
if (!FastByteArrayCompare(oldEdited, newEdited))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
message.RawData = newEdited;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
|
||||
if (!FastByteArrayCompare(originalMessageData, message.RawData))
|
||||
{
|
||||
allocatedString = Service<LibcFunction>.Get().NewString(message.RawData);
|
||||
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||
messagePtr = allocatedString.Address;
|
||||
}
|
||||
|
||||
var newEditedSender = parsedSender.Encode();
|
||||
if (!FastByteArrayCompare(oldEditedSender, newEditedSender))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
sender.RawData = newEditedSender;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
|
||||
if (!FastByteArrayCompare(originalSenderData, sender.RawData))
|
||||
{
|
||||
allocatedStringSender = Service<LibcFunction>.Get().NewString(sender.RawData);
|
||||
Log.Debug(
|
||||
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
||||
senderPtr = allocatedStringSender.Address;
|
||||
}
|
||||
|
||||
// Print the original chat if it's handled.
|
||||
if (isHandled)
|
||||
{
|
||||
this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter);
|
||||
this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
this.baseAddress = manager;
|
||||
|
||||
allocatedString?.Dispose();
|
||||
allocatedStringSender?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||
var messageSize = 0;
|
||||
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
|
||||
var payloadBytes = new byte[messageSize];
|
||||
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
|
||||
var seStr = SeString.Parse(payloadBytes);
|
||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||
{
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
}
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,120 +1,117 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.Internal;
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
namespace Dalamud.Game.Gui
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// Initializes a new instance of the <see cref="ChatGuiAddressResolver"/> class.
|
||||
/// </summary>
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
/// <param name="baseAddress">The base address of the native ChatManager class.</param>
|
||||
public ChatGuiAddressResolver(IntPtr baseAddress)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatGuiAddressResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">The base address of the native ChatManager class.</param>
|
||||
public ChatGuiAddressResolver(IntPtr baseAddress)
|
||||
{
|
||||
this.BaseAddress = baseAddress;
|
||||
}
|
||||
this.BaseAddress = baseAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base address of the native ChatManager class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; }
|
||||
/// <summary>
|
||||
/// Gets the base address of the native ChatManager class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native PrintMessage method.
|
||||
/// </summary>
|
||||
public IntPtr PrintMessage { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native PrintMessage method.
|
||||
/// </summary>
|
||||
public IntPtr PrintMessage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native PopulateItemLinkObject method.
|
||||
/// </summary>
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native PopulateItemLinkObject method.
|
||||
/// </summary>
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native InteractableLinkClicked method.
|
||||
/// </summary>
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native InteractableLinkClicked method.
|
||||
/// </summary>
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
|
||||
/*
|
||||
--- for reference: 4.57 ---
|
||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||
.text:00000001405CD210 var_210 = word ptr -210h
|
||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||
.text:00000001405CD210 var_200 = word ptr -200h
|
||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||
.text:00000001405CD210 push rbp
|
||||
.text:00000001405CD212 push rdi
|
||||
.text:00000001405CD213 push r14
|
||||
.text:00000001405CD215 push r15
|
||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||
.text:00000001405CD21F sub rsp, 228h
|
||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||
.text:00000001405CD22D xor rax, rsp
|
||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||
.text:00000001405CD237 xor r10b, r10b
|
||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||
.text:00000001405CD23F xor eax, eax
|
||||
.text:00000001405CD241 mov r11, r9
|
||||
.text:00000001405CD244 mov r14, r8
|
||||
.text:00000001405CD247 mov r9d, eax
|
||||
.text:00000001405CD24A movzx r15d, dx
|
||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||
.text:00000001405CD255 mov rdi, rcx
|
||||
*/
|
||||
/*
|
||||
--- for reference: 4.57 ---
|
||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||
.text:00000001405CD210 var_210 = word ptr -210h
|
||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||
.text:00000001405CD210 var_200 = word ptr -200h
|
||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||
.text:00000001405CD210 push rbp
|
||||
.text:00000001405CD212 push rdi
|
||||
.text:00000001405CD213 push r14
|
||||
.text:00000001405CD215 push r15
|
||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||
.text:00000001405CD21F sub rsp, 228h
|
||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||
.text:00000001405CD22D xor rax, rsp
|
||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||
.text:00000001405CD237 xor r10b, r10b
|
||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||
.text:00000001405CD23F xor eax, eax
|
||||
.text:00000001405CD241 mov r11, r9
|
||||
.text:00000001405CD244 mov r14, r8
|
||||
.text:00000001405CD247 mov r9d, eax
|
||||
.text:00000001405CD24A movzx r15d, dx
|
||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||
.text:00000001405CD255 mov rdi, rcx
|
||||
*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
||||
this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05");
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
||||
this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05");
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||
|
||||
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||
|
||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||
}
|
||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,303 +9,302 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Memory;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native in-game "fly text".
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class FlyTextGui : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native in-game "fly text".
|
||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class FlyTextGui : IDisposable
|
||||
private readonly AddFlyTextDelegate addFlyTextNative;
|
||||
|
||||
/// <summary>
|
||||
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
||||
/// </summary>
|
||||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextGui"/> class.
|
||||
/// </summary>
|
||||
internal FlyTextGui()
|
||||
{
|
||||
/// <summary>
|
||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||
/// </summary>
|
||||
private readonly AddFlyTextDelegate addFlyTextNative;
|
||||
this.Address = new FlyTextGuiAddressResolver();
|
||||
this.Address.Setup();
|
||||
|
||||
/// <summary>
|
||||
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
||||
/// </summary>
|
||||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||
this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextGui"/> class.
|
||||
/// </summary>
|
||||
internal FlyTextGui()
|
||||
/// <summary>
|
||||
/// The delegate defining the type for the FlyText event.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
|
||||
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
|
||||
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
|
||||
public delegate void OnFlyTextCreatedDelegate(
|
||||
ref FlyTextKind kind,
|
||||
ref int val1,
|
||||
ref int val2,
|
||||
ref SeString text1,
|
||||
ref SeString text2,
|
||||
ref uint color,
|
||||
ref uint icon,
|
||||
ref float yOffset,
|
||||
ref bool handled);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native CreateFlyText function's hook.
|
||||
/// </summary>
|
||||
private delegate IntPtr CreateFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native AddFlyText function pointer.
|
||||
/// </summary>
|
||||
private delegate void AddFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
uint actorIndex,
|
||||
uint messageMax,
|
||||
IntPtr numbers,
|
||||
uint offsetNum,
|
||||
uint offsetNumMax,
|
||||
IntPtr strings,
|
||||
uint offsetStr,
|
||||
uint offsetStrMax,
|
||||
int unknown);
|
||||
|
||||
/// <summary>
|
||||
/// The FlyText event that can be subscribed to.
|
||||
/// </summary>
|
||||
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
private Dalamud Dalamud { get; }
|
||||
|
||||
private FlyTextGuiAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.createFlyTextHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a fly text in-game on the local player.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon)
|
||||
{
|
||||
// Known valid flytext region within the atk arrays
|
||||
var numIndex = 28;
|
||||
var strIndex = 25;
|
||||
var numOffset = 147u;
|
||||
var strOffset = 28u;
|
||||
|
||||
// Get the UI module and flytext addon pointers
|
||||
var gameGui = Service<GameGui>.Get();
|
||||
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||
var flytext = gameGui.GetAddonByName("_FlyText", 1);
|
||||
|
||||
if (ui == null || flytext == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
// Get the number and string arrays we need
|
||||
var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder;
|
||||
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||
|
||||
// Write the values to the arrays using a known valid flytext region
|
||||
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
||||
numArray->IntArray[numOffset + 1] = (int)kind;
|
||||
numArray->IntArray[numOffset + 2] = unchecked((int)val1);
|
||||
numArray->IntArray[numOffset + 3] = unchecked((int)val2);
|
||||
numArray->IntArray[numOffset + 4] = 5; // Unknown
|
||||
numArray->IntArray[numOffset + 5] = unchecked((int)color);
|
||||
numArray->IntArray[numOffset + 6] = unchecked((int)icon);
|
||||
numArray->IntArray[numOffset + 7] = 0; // Unknown
|
||||
numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset
|
||||
|
||||
fixed (byte* pText1 = text1.Encode())
|
||||
{
|
||||
this.Address = new FlyTextGuiAddressResolver();
|
||||
this.Address.Setup();
|
||||
|
||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||
this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate defining the type for the FlyText event.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
|
||||
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
|
||||
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
|
||||
public delegate void OnFlyTextCreatedDelegate(
|
||||
ref FlyTextKind kind,
|
||||
ref int val1,
|
||||
ref int val2,
|
||||
ref SeString text1,
|
||||
ref SeString text2,
|
||||
ref uint color,
|
||||
ref uint icon,
|
||||
ref float yOffset,
|
||||
ref bool handled);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native CreateFlyText function's hook.
|
||||
/// </summary>
|
||||
private delegate IntPtr CreateFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native AddFlyText function pointer.
|
||||
/// </summary>
|
||||
private delegate void AddFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
uint actorIndex,
|
||||
uint messageMax,
|
||||
IntPtr numbers,
|
||||
uint offsetNum,
|
||||
uint offsetNumMax,
|
||||
IntPtr strings,
|
||||
uint offsetStr,
|
||||
uint offsetStrMax,
|
||||
int unknown);
|
||||
|
||||
/// <summary>
|
||||
/// The FlyText event that can be subscribed to.
|
||||
/// </summary>
|
||||
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
private Dalamud Dalamud { get; }
|
||||
|
||||
private FlyTextGuiAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.createFlyTextHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a fly text in-game on the local player.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon)
|
||||
{
|
||||
// Known valid flytext region within the atk arrays
|
||||
var numIndex = 28;
|
||||
var strIndex = 25;
|
||||
var numOffset = 147u;
|
||||
var strOffset = 28u;
|
||||
|
||||
// Get the UI module and flytext addon pointers
|
||||
var gameGui = Service<GameGui>.Get();
|
||||
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||
var flytext = gameGui.GetAddonByName("_FlyText", 1);
|
||||
|
||||
if (ui == null || flytext == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
// Get the number and string arrays we need
|
||||
var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder;
|
||||
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||
|
||||
// Write the values to the arrays using a known valid flytext region
|
||||
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
||||
numArray->IntArray[numOffset + 1] = (int)kind;
|
||||
numArray->IntArray[numOffset + 2] = unchecked((int)val1);
|
||||
numArray->IntArray[numOffset + 3] = unchecked((int)val2);
|
||||
numArray->IntArray[numOffset + 4] = 5; // Unknown
|
||||
numArray->IntArray[numOffset + 5] = unchecked((int)color);
|
||||
numArray->IntArray[numOffset + 6] = unchecked((int)icon);
|
||||
numArray->IntArray[numOffset + 7] = 0; // Unknown
|
||||
numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset
|
||||
|
||||
fixed (byte* pText1 = text1.Encode())
|
||||
fixed (byte* pText2 = text2.Encode())
|
||||
{
|
||||
fixed (byte* pText2 = text2.Encode())
|
||||
{
|
||||
strArray->StringArray[strOffset + 0] = pText1;
|
||||
strArray->StringArray[strOffset + 1] = pText2;
|
||||
strArray->StringArray[strOffset + 0] = pText1;
|
||||
strArray->StringArray[strOffset + 1] = pText2;
|
||||
|
||||
this.addFlyTextNative(
|
||||
flytext,
|
||||
actorIndex,
|
||||
1,
|
||||
(IntPtr)numArray,
|
||||
numOffset,
|
||||
9,
|
||||
(IntPtr)strArray,
|
||||
strOffset,
|
||||
2,
|
||||
0);
|
||||
}
|
||||
this.addFlyTextNative(
|
||||
flytext,
|
||||
actorIndex,
|
||||
1,
|
||||
(IntPtr)numArray,
|
||||
numOffset,
|
||||
9,
|
||||
(IntPtr)strArray,
|
||||
strOffset,
|
||||
2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
internal void Enable()
|
||||
{
|
||||
this.createFlyTextHook.Enable();
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private IntPtr CreateFlyTextDetour(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
||||
|
||||
var handled = false;
|
||||
|
||||
var tmpKind = kind;
|
||||
var tmpVal1 = val1;
|
||||
var tmpVal2 = val2;
|
||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
||||
var tmpColor = color;
|
||||
var tmpIcon = icon;
|
||||
var tmpYOffset = yOffset;
|
||||
|
||||
var cmpText1 = tmpText1.ToString();
|
||||
var cmpText2 = tmpText2.ToString();
|
||||
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
||||
$"kind({kind}) val1({val1}) val2({val2}) " +
|
||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
||||
Log.Verbose("[FlyText] Calling flytext events!");
|
||||
this.FlyTextCreated?.Invoke(
|
||||
ref tmpKind,
|
||||
ref tmpVal1,
|
||||
ref tmpVal2,
|
||||
ref tmpText1,
|
||||
ref tmpText2,
|
||||
ref tmpColor,
|
||||
ref tmpIcon,
|
||||
ref tmpYOffset,
|
||||
ref handled);
|
||||
|
||||
// If handled, ignore the original call
|
||||
if (handled)
|
||||
{
|
||||
Log.Verbose("[FlyText] FlyText was handled.");
|
||||
|
||||
// Returning null to AddFlyText from CreateFlyText will result
|
||||
// in the operation being dropped entirely.
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
// Check if any values have changed
|
||||
var dirty = tmpKind != kind ||
|
||||
tmpVal1 != val1 ||
|
||||
tmpVal2 != val2 ||
|
||||
tmpText1.ToString() != cmpText1 ||
|
||||
tmpText2.ToString() != cmpText2 ||
|
||||
tmpColor != color ||
|
||||
tmpIcon != icon ||
|
||||
Math.Abs(tmpYOffset - yOffset) > float.Epsilon;
|
||||
|
||||
// If not dirty, make the original call
|
||||
if (!dirty)
|
||||
{
|
||||
Log.Verbose("[FlyText] Calling flytext with original args.");
|
||||
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset);
|
||||
}
|
||||
|
||||
var terminated1 = Terminate(tmpText1.Encode());
|
||||
var terminated2 = Terminate(tmpText2.Encode());
|
||||
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
||||
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
||||
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
||||
Log.Verbose("[FlyText] Allocated and set strings.");
|
||||
|
||||
retVal = this.createFlyTextHook.Original(
|
||||
addonFlyText,
|
||||
tmpKind,
|
||||
tmpVal1,
|
||||
tmpVal2,
|
||||
pText2,
|
||||
tmpColor,
|
||||
tmpIcon,
|
||||
pText1,
|
||||
tmpYOffset);
|
||||
|
||||
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
||||
|
||||
Task.Delay(2000).ContinueWith(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Marshal.FreeHGlobal(pText1);
|
||||
Marshal.FreeHGlobal(pText2);
|
||||
Log.Verbose("[FlyText] Freed strings.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task.");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception occurred in CreateFlyTextDetour!");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
internal void Enable()
|
||||
{
|
||||
this.createFlyTextHook.Enable();
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private IntPtr CreateFlyTextDetour(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
||||
|
||||
var handled = false;
|
||||
|
||||
var tmpKind = kind;
|
||||
var tmpVal1 = val1;
|
||||
var tmpVal2 = val2;
|
||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
||||
var tmpColor = color;
|
||||
var tmpIcon = icon;
|
||||
var tmpYOffset = yOffset;
|
||||
|
||||
var cmpText1 = tmpText1.ToString();
|
||||
var cmpText2 = tmpText2.ToString();
|
||||
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
||||
$"kind({kind}) val1({val1}) val2({val2}) " +
|
||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
||||
Log.Verbose("[FlyText] Calling flytext events!");
|
||||
this.FlyTextCreated?.Invoke(
|
||||
ref tmpKind,
|
||||
ref tmpVal1,
|
||||
ref tmpVal2,
|
||||
ref tmpText1,
|
||||
ref tmpText2,
|
||||
ref tmpColor,
|
||||
ref tmpIcon,
|
||||
ref tmpYOffset,
|
||||
ref handled);
|
||||
|
||||
// If handled, ignore the original call
|
||||
if (handled)
|
||||
{
|
||||
Log.Verbose("[FlyText] FlyText was handled.");
|
||||
|
||||
// Returning null to AddFlyText from CreateFlyText will result
|
||||
// in the operation being dropped entirely.
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
// Check if any values have changed
|
||||
var dirty = tmpKind != kind ||
|
||||
tmpVal1 != val1 ||
|
||||
tmpVal2 != val2 ||
|
||||
tmpText1.ToString() != cmpText1 ||
|
||||
tmpText2.ToString() != cmpText2 ||
|
||||
tmpColor != color ||
|
||||
tmpIcon != icon ||
|
||||
Math.Abs(tmpYOffset - yOffset) > float.Epsilon;
|
||||
|
||||
// If not dirty, make the original call
|
||||
if (!dirty)
|
||||
{
|
||||
Log.Verbose("[FlyText] Calling flytext with original args.");
|
||||
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset);
|
||||
}
|
||||
|
||||
var terminated1 = Terminate(tmpText1.Encode());
|
||||
var terminated2 = Terminate(tmpText2.Encode());
|
||||
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
||||
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
||||
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
||||
Log.Verbose("[FlyText] Allocated and set strings.");
|
||||
|
||||
retVal = this.createFlyTextHook.Original(
|
||||
addonFlyText,
|
||||
tmpKind,
|
||||
tmpVal1,
|
||||
tmpVal2,
|
||||
pText2,
|
||||
tmpColor,
|
||||
tmpIcon,
|
||||
pText1,
|
||||
tmpYOffset);
|
||||
|
||||
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
||||
|
||||
Task.Delay(2000).ContinueWith(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Marshal.FreeHGlobal(pText1);
|
||||
Marshal.FreeHGlobal(pText2);
|
||||
Log.Verbose("[FlyText] Freed strings.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task.");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception occurred in CreateFlyTextDetour!");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||
/// </summary>
|
||||
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||
/// Gets the address of the native AddFlyText method, which occurs
|
||||
/// when the game adds fly text elements to the UI. Multiple fly text
|
||||
/// elements can be added in a single AddFlyText call.
|
||||
/// </summary>
|
||||
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||
public IntPtr AddFlyText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native CreateFlyText method, which occurs
|
||||
/// when the game creates a new fly text element. This method is called
|
||||
/// once per fly text element, and can be called multiple times per
|
||||
/// AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr CreateFlyText { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native AddFlyText method, which occurs
|
||||
/// when the game adds fly text elements to the UI. Multiple fly text
|
||||
/// elements can be added in a single AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr AddFlyText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native CreateFlyText method, which occurs
|
||||
/// when the game creates a new fly text element. This method is called
|
||||
/// once per fly text element, and can be called multiple times per
|
||||
/// AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr CreateFlyText { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
|
||||
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
|
||||
}
|
||||
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
|
||||
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,283 +1,282 @@
|
|||
namespace Dalamud.Game.Gui.FlyText
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// Enum of FlyTextKind values. Members suffixed with
|
||||
/// a number seem to be a duplicate, or perform duplicate behavior.
|
||||
/// </summary>
|
||||
public enum FlyTextKind : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum of FlyTextKind values. Members suffixed with
|
||||
/// a number seem to be a duplicate, or perform duplicate behavior.
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Used for autos and incoming DoTs.
|
||||
/// </summary>
|
||||
public enum FlyTextKind : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Used for autos and incoming DoTs.
|
||||
/// </summary>
|
||||
AutoAttack = 0,
|
||||
AutoAttack = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit = 1,
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit = 2,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
||||
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit = 3,
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
||||
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit = 3,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedAttack = 4,
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedAttack = 4,
|
||||
|
||||
/// <summary>
|
||||
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedDirectHit = 5,
|
||||
/// <summary>
|
||||
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedDirectHit = 5,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalHit = 6,
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalHit = 6,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalDirectHit = 7,
|
||||
/// <summary>
|
||||
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalDirectHit = 7,
|
||||
|
||||
/// <summary>
|
||||
/// All caps, serif MISS.
|
||||
/// </summary>
|
||||
Miss = 8,
|
||||
/// <summary>
|
||||
/// All caps, serif MISS.
|
||||
/// </summary>
|
||||
Miss = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif MISS.
|
||||
/// </summary>
|
||||
NamedMiss = 9,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif MISS.
|
||||
/// </summary>
|
||||
NamedMiss = 9,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif DODGE.
|
||||
/// </summary>
|
||||
Dodge = 10,
|
||||
/// <summary>
|
||||
/// All caps serif DODGE.
|
||||
/// </summary>
|
||||
Dodge = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif DODGE.
|
||||
/// </summary>
|
||||
NamedDodge = 11,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif DODGE.
|
||||
/// </summary>
|
||||
NamedDodge = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1.
|
||||
/// </summary>
|
||||
NamedIcon = 12,
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1.
|
||||
/// </summary>
|
||||
NamedIcon = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 (2).
|
||||
/// </summary>
|
||||
NamedIcon2 = 13,
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 (2).
|
||||
/// </summary>
|
||||
NamedIcon2 = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
Exp = 14,
|
||||
/// <summary>
|
||||
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
Exp = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp = 15,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp = 16,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp = 16,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedAttack2 = 17,
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedAttack2 = 17,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedMp2 = 18,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedMp2 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedTp2 = 19,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedTp2 = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedEp = 20,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedEp = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Displays nothing.
|
||||
/// </summary>
|
||||
None = 21,
|
||||
/// <summary>
|
||||
/// Displays nothing.
|
||||
/// </summary>
|
||||
None = 21,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif INVULNERABLE.
|
||||
/// </summary>
|
||||
Invulnerable = 22,
|
||||
/// <summary>
|
||||
/// All caps serif INVULNERABLE.
|
||||
/// </summary>
|
||||
Invulnerable = 22,
|
||||
|
||||
/// <summary>
|
||||
/// All caps sans-serif condensed font INTERRUPTED!
|
||||
/// Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
Interrupted = 23,
|
||||
/// <summary>
|
||||
/// All caps sans-serif condensed font INTERRUPTED!
|
||||
/// Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
Interrupted = 23,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2.
|
||||
/// </summary>
|
||||
AutoAttackNoText = 24,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2.
|
||||
/// </summary>
|
||||
AutoAttackNoText = 24,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (2).
|
||||
/// </summary>
|
||||
AutoAttackNoText2 = 25,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (2).
|
||||
/// </summary>
|
||||
AutoAttackNoText2 = 25,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
||||
/// </summary>
|
||||
CriticalHit2 = 26,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
||||
/// </summary>
|
||||
CriticalHit2 = 26,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (3).
|
||||
/// </summary>
|
||||
AutoAttackNoText3 = 27,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (3).
|
||||
/// </summary>
|
||||
AutoAttackNoText3 = 27,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedCriticalHit2 = 28,
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedCriticalHit2 = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithMp = 29,
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithMp = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithTp = 30,
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithTp = 30,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedIconHasNoEffect = 31,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedIconHasNoEffect = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded = 32,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded (2).
|
||||
/// Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded2 = 33,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded (2).
|
||||
/// Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded2 = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 in sans-serif font.
|
||||
/// </summary>
|
||||
Named = 34,
|
||||
/// <summary>
|
||||
/// Text1 in sans-serif font.
|
||||
/// </summary>
|
||||
Named = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedIconFullyResisted = 35,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedIconFullyResisted = 35,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif 'INCAPACITATED!'.
|
||||
/// </summary>
|
||||
Incapacitated = 36,
|
||||
/// <summary>
|
||||
/// All caps serif 'INCAPACITATED!'.
|
||||
/// </summary>
|
||||
Incapacitated = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedFullyResisted = 37,
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedFullyResisted = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedHasNoEffect = 38,
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedHasNoEffect = 38,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
||||
/// </summary>
|
||||
NamedAttack3 = 39,
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
||||
/// </summary>
|
||||
NamedAttack3 = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedMp3 = 40,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedMp3 = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedTp3 = 41,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedTp3 = 41,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
||||
/// </summary>
|
||||
NamedIconInvulnerable = 42,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
||||
/// </summary>
|
||||
NamedIconInvulnerable = 42,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif RESIST.
|
||||
/// </summary>
|
||||
Resist = 43,
|
||||
/// <summary>
|
||||
/// All caps serif RESIST.
|
||||
/// </summary>
|
||||
Resist = 43,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but places the given icon in the item icon outline.
|
||||
/// </summary>
|
||||
NamedIconWithItemOutline = 44,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but places the given icon in the item icon outline.
|
||||
/// </summary>
|
||||
NamedIconWithItemOutline = 44,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (4).
|
||||
/// </summary>
|
||||
AutoAttackNoText4 = 45,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (4).
|
||||
/// </summary>
|
||||
AutoAttackNoText4 = 45,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit3 = 46,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit3 = 46,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif REFLECT.
|
||||
/// </summary>
|
||||
Reflect = 47,
|
||||
/// <summary>
|
||||
/// All caps serif REFLECT.
|
||||
/// </summary>
|
||||
Reflect = 47,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif REFLECTED.
|
||||
/// </summary>
|
||||
Reflected = 48,
|
||||
/// <summary>
|
||||
/// All caps serif REFLECTED.
|
||||
/// </summary>
|
||||
Reflected = 48,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit2 = 49,
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit2 = 49,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit4 = 50,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit4 = 50,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit2 = 51,
|
||||
}
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit2 = 51,
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,104 +1,103 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="GameGui"/> class.
|
||||
/// </summary>
|
||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="GameGui"/> class.
|
||||
/// Initializes a new instance of the <see cref="GameGuiAddressResolver"/> class.
|
||||
/// </summary>
|
||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||
public GameGuiAddressResolver()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameGuiAddressResolver"/> class.
|
||||
/// </summary>
|
||||
public GameGuiAddressResolver()
|
||||
{
|
||||
this.BaseAddress = Service<Framework>.Get().Address.BaseAddress;
|
||||
}
|
||||
this.BaseAddress = Service<Framework>.Get().Address.BaseAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base address of the native GuiManager class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the base address of the native GuiManager class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ChatManager class.
|
||||
/// </summary>
|
||||
public IntPtr ChatManager { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native ChatManager class.
|
||||
/// </summary>
|
||||
public IntPtr ChatManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native SetGlobalBgm method.
|
||||
/// </summary>
|
||||
public IntPtr SetGlobalBgm { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native SetGlobalBgm method.
|
||||
/// </summary>
|
||||
public IntPtr SetGlobalBgm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemHover { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemOut { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionHover { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionOut { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleImm method.
|
||||
/// </summary>
|
||||
public IntPtr HandleImm { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleImm method.
|
||||
/// </summary>
|
||||
public IntPtr HandleImm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetMatrixSingleton method.
|
||||
/// </summary>
|
||||
public IntPtr GetMatrixSingleton { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetMatrixSingleton method.
|
||||
/// </summary>
|
||||
public IntPtr GetMatrixSingleton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ScreenToWorld method.
|
||||
/// </summary>
|
||||
public IntPtr ScreenToWorld { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native ScreenToWorld method.
|
||||
/// </summary>
|
||||
public IntPtr ScreenToWorld { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ToggleUiHide method.
|
||||
/// </summary>
|
||||
public IntPtr ToggleUiHide { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native ToggleUiHide method.
|
||||
/// </summary>
|
||||
public IntPtr ToggleUiHide { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetAgentModule method.
|
||||
/// </summary>
|
||||
public IntPtr GetAgentModule { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetAgentModule method.
|
||||
/// </summary>
|
||||
public IntPtr GetAgentModule { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
|
||||
var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28");
|
||||
this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size);
|
||||
}
|
||||
var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28");
|
||||
this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SetupInternal(SigScanner scanner)
|
||||
{
|
||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
||||
this.ChatManager = this.BaseAddress + 0x13E0;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override void SetupInternal(SigScanner scanner)
|
||||
{
|
||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
||||
this.ChatManager = this.BaseAddress + 0x13E0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,48 @@
|
|||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// ActionKinds used in AgentActionDetail.
|
||||
/// These describe the possible kinds of actions being hovered.
|
||||
/// </summary>
|
||||
public enum HoverActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// ActionKinds used in AgentActionDetail.
|
||||
/// These describe the possible kinds of actions being hovered.
|
||||
/// No action is hovered.
|
||||
/// </summary>
|
||||
public enum HoverActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// No action is hovered.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A regular action is hovered.
|
||||
/// </summary>
|
||||
Action = 21,
|
||||
/// <summary>
|
||||
/// A regular action is hovered.
|
||||
/// </summary>
|
||||
Action = 21,
|
||||
|
||||
/// <summary>
|
||||
/// A general action is hovered.
|
||||
/// </summary>
|
||||
GeneralAction = 23,
|
||||
/// <summary>
|
||||
/// A general action is hovered.
|
||||
/// </summary>
|
||||
GeneralAction = 23,
|
||||
|
||||
/// <summary>
|
||||
/// A companion order type of action is hovered.
|
||||
/// </summary>
|
||||
CompanionOrder = 24,
|
||||
/// <summary>
|
||||
/// A companion order type of action is hovered.
|
||||
/// </summary>
|
||||
CompanionOrder = 24,
|
||||
|
||||
/// <summary>
|
||||
/// A main command type of action is hovered.
|
||||
/// </summary>
|
||||
MainCommand = 25,
|
||||
/// <summary>
|
||||
/// A main command type of action is hovered.
|
||||
/// </summary>
|
||||
MainCommand = 25,
|
||||
|
||||
/// <summary>
|
||||
/// An extras command type of action is hovered.
|
||||
/// </summary>
|
||||
ExtraCommand = 26,
|
||||
/// <summary>
|
||||
/// An extras command type of action is hovered.
|
||||
/// </summary>
|
||||
ExtraCommand = 26,
|
||||
|
||||
/// <summary>
|
||||
/// A pet order type of action is hovered.
|
||||
/// </summary>
|
||||
PetOrder = 28,
|
||||
/// <summary>
|
||||
/// A pet order type of action is hovered.
|
||||
/// </summary>
|
||||
PetOrder = 28,
|
||||
|
||||
/// <summary>
|
||||
/// A trait is hovered.
|
||||
/// </summary>
|
||||
Trait = 29,
|
||||
}
|
||||
/// <summary>
|
||||
/// A trait is hovered.
|
||||
/// </summary>
|
||||
Trait = 29,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the hotbar action currently hovered over by the cursor.
|
||||
/// </summary>
|
||||
public class HoveredAction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the hotbar action currently hovered over by the cursor.
|
||||
/// Gets or sets the base action ID.
|
||||
/// </summary>
|
||||
public class HoveredAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the base action ID.
|
||||
/// </summary>
|
||||
public uint BaseActionID { get; set; } = 0;
|
||||
public uint BaseActionID { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action ID accounting for automatic upgrades.
|
||||
/// </summary>
|
||||
public uint ActionID { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the action ID accounting for automatic upgrades.
|
||||
/// </summary>
|
||||
public uint ActionID { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of action.
|
||||
/// </summary>
|
||||
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the type of action.
|
||||
/// </summary>
|
||||
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,235 +9,234 @@ using ImGuiNET;
|
|||
|
||||
using static Dalamud.NativeFunctions;
|
||||
|
||||
namespace Dalamud.Game.Gui.Internal
|
||||
namespace Dalamud.Game.Gui.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles IME for non-English users.
|
||||
/// </summary>
|
||||
internal class DalamudIME : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("IME");
|
||||
|
||||
private IntPtr interfaceHandle;
|
||||
private IntPtr wndProcPtr;
|
||||
private IntPtr oldWndProcPtr;
|
||||
private WndProcDelegate wndProcDelegate;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles IME for non-English users.
|
||||
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
||||
/// </summary>
|
||||
internal class DalamudIME : IDisposable
|
||||
internal DalamudIME()
|
||||
{
|
||||
private static readonly ModuleLog Log = new("IME");
|
||||
}
|
||||
|
||||
private IntPtr interfaceHandle;
|
||||
private IntPtr wndProcPtr;
|
||||
private IntPtr oldWndProcPtr;
|
||||
private WndProcDelegate wndProcDelegate;
|
||||
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
||||
/// </summary>
|
||||
internal DalamudIME()
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the module is enabled.
|
||||
/// </summary>
|
||||
internal bool IsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the first imm candidate in relation to the full list.
|
||||
/// </summary>
|
||||
internal CandidateList ImmCandNative { get; private set; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the imm candidates.
|
||||
/// </summary>
|
||||
internal List<string> ImmCand { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected imm component.
|
||||
/// </summary>
|
||||
internal string ImmComp { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.oldWndProcPtr != IntPtr.Zero)
|
||||
{
|
||||
}
|
||||
|
||||
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the module is enabled.
|
||||
/// </summary>
|
||||
internal bool IsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the first imm candidate in relation to the full list.
|
||||
/// </summary>
|
||||
internal CandidateList ImmCandNative { get; private set; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the imm candidates.
|
||||
/// </summary>
|
||||
internal List<string> ImmCand { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected imm component.
|
||||
/// </summary>
|
||||
internal string ImmComp { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.oldWndProcPtr != IntPtr.Zero)
|
||||
{
|
||||
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
|
||||
this.oldWndProcPtr = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the IME module.
|
||||
/// </summary>
|
||||
internal void Enable()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.wndProcDelegate = this.WndProcDetour;
|
||||
this.interfaceHandle = Service<InterfaceManager>.Get().WindowHandlePtr;
|
||||
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
|
||||
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);
|
||||
this.IsEnabled = true;
|
||||
Log.Information("Enabled!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Enable failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleWindow(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
Service<DalamudInterface>.Get().OpenIMEWindow();
|
||||
else
|
||||
Service<DalamudInterface>.Get().CloseIMEWindow();
|
||||
}
|
||||
|
||||
private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
var wmsg = (WindowsMessage)msg;
|
||||
|
||||
switch (wmsg)
|
||||
{
|
||||
case WindowsMessage.WM_IME_NOTIFY:
|
||||
switch ((IMECommand)wParam)
|
||||
{
|
||||
case IMECommand.ChangeCandidate:
|
||||
this.ToggleWindow(true);
|
||||
|
||||
if (hWnd == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
var candlistPtr = Marshal.AllocHGlobal((int)size);
|
||||
size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size);
|
||||
|
||||
var candlist = this.ImmCandNative = Marshal.PtrToStructure<CandidateList>(candlistPtr);
|
||||
var pageSize = candlist.PageSize;
|
||||
var candCount = candlist.Count;
|
||||
|
||||
if (pageSize > 0 && candCount > 1)
|
||||
{
|
||||
var dwOffsets = new int[candCount];
|
||||
for (var i = 0; i < candCount; i++)
|
||||
{
|
||||
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
|
||||
}
|
||||
|
||||
var pageStart = candlist.PageStart;
|
||||
|
||||
var cand = new string[pageSize];
|
||||
this.ImmCand.Clear();
|
||||
|
||||
for (var i = 0; i < pageSize; i++)
|
||||
{
|
||||
var offStart = dwOffsets[i + pageStart];
|
||||
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
|
||||
|
||||
var pStrStart = candlistPtr + (int)offStart;
|
||||
var pStrEnd = candlistPtr + (int)offEnd;
|
||||
|
||||
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
|
||||
if (len > 0)
|
||||
{
|
||||
var candBytes = new byte[len];
|
||||
Marshal.Copy(pStrStart, candBytes, 0, len);
|
||||
|
||||
var candStr = Encoding.Unicode.GetString(candBytes);
|
||||
cand[i] = candStr;
|
||||
|
||||
this.ImmCand.Add(candStr);
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(candlistPtr);
|
||||
}
|
||||
|
||||
break;
|
||||
case IMECommand.OpenCandidate:
|
||||
this.ToggleWindow(true);
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
case IMECommand.CloseCandidate:
|
||||
this.ToggleWindow(false);
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case WindowsMessage.WM_IME_COMPOSITION:
|
||||
if ((lParam & (long)IMEComposition.ResultStr) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
io.AddInputCharactersUTF8(lpstr);
|
||||
|
||||
this.ImmComp = string.Empty;
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
|
||||
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
||||
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
this.ImmComp = lpstr;
|
||||
if (lpstr == string.Empty)
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
||||
}
|
||||
|
||||
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
|
||||
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
|
||||
this.oldWndProcPtr = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the IME module.
|
||||
/// </summary>
|
||||
internal void Enable()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.wndProcDelegate = this.WndProcDetour;
|
||||
this.interfaceHandle = Service<InterfaceManager>.Get().WindowHandlePtr;
|
||||
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
|
||||
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);
|
||||
this.IsEnabled = true;
|
||||
Log.Information("Enabled!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Enable failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleWindow(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
Service<DalamudInterface>.Get().OpenIMEWindow();
|
||||
else
|
||||
Service<DalamudInterface>.Get().CloseIMEWindow();
|
||||
}
|
||||
|
||||
private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
var wmsg = (WindowsMessage)msg;
|
||||
|
||||
switch (wmsg)
|
||||
{
|
||||
case WindowsMessage.WM_IME_NOTIFY:
|
||||
switch ((IMECommand)wParam)
|
||||
{
|
||||
case IMECommand.ChangeCandidate:
|
||||
this.ToggleWindow(true);
|
||||
|
||||
if (hWnd == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
var candlistPtr = Marshal.AllocHGlobal((int)size);
|
||||
size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size);
|
||||
|
||||
var candlist = this.ImmCandNative = Marshal.PtrToStructure<CandidateList>(candlistPtr);
|
||||
var pageSize = candlist.PageSize;
|
||||
var candCount = candlist.Count;
|
||||
|
||||
if (pageSize > 0 && candCount > 1)
|
||||
{
|
||||
var dwOffsets = new int[candCount];
|
||||
for (var i = 0; i < candCount; i++)
|
||||
{
|
||||
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
|
||||
}
|
||||
|
||||
var pageStart = candlist.PageStart;
|
||||
|
||||
var cand = new string[pageSize];
|
||||
this.ImmCand.Clear();
|
||||
|
||||
for (var i = 0; i < pageSize; i++)
|
||||
{
|
||||
var offStart = dwOffsets[i + pageStart];
|
||||
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
|
||||
|
||||
var pStrStart = candlistPtr + (int)offStart;
|
||||
var pStrEnd = candlistPtr + (int)offEnd;
|
||||
|
||||
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
|
||||
if (len > 0)
|
||||
{
|
||||
var candBytes = new byte[len];
|
||||
Marshal.Copy(pStrStart, candBytes, 0, len);
|
||||
|
||||
var candStr = Encoding.Unicode.GetString(candBytes);
|
||||
cand[i] = candStr;
|
||||
|
||||
this.ImmCand.Add(candStr);
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(candlistPtr);
|
||||
}
|
||||
|
||||
break;
|
||||
case IMECommand.OpenCandidate:
|
||||
this.ToggleWindow(true);
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
case IMECommand.CloseCandidate:
|
||||
this.ToggleWindow(false);
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case WindowsMessage.WM_IME_COMPOSITION:
|
||||
if ((lParam & (long)IMEComposition.ResultStr) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
io.AddInputCharactersUTF8(lpstr);
|
||||
|
||||
this.ImmComp = string.Empty;
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
|
||||
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
||||
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
this.ImmComp = lpstr;
|
||||
if (lpstr == string.Empty)
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
||||
}
|
||||
|
||||
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// The structure of the PartyFinder packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacket
|
||||
{
|
||||
/// <summary>
|
||||
/// The structure of the PartyFinder packet.
|
||||
/// Gets the size of this packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacket
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the size of this packet.
|
||||
/// </summary>
|
||||
internal static int PacketSize { get; } = Marshal.SizeOf<PartyFinderPacket>();
|
||||
internal static int PacketSize { get; } = Marshal.SizeOf<PartyFinderPacket>();
|
||||
|
||||
internal readonly int BatchNumber;
|
||||
internal readonly int BatchNumber;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] padding1;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] padding1;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
internal readonly PartyFinderPacketListing[] Listings;
|
||||
}
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
internal readonly PartyFinderPacketListing[] Listings;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,98 +2,97 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// The structure of an individual listing within a packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacketListing
|
||||
{
|
||||
/// <summary>
|
||||
/// The structure of an individual listing within a packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacketListing
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header1;
|
||||
internal readonly uint Id;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header2;
|
||||
|
||||
internal readonly uint ContentIdLower;
|
||||
private readonly ushort unknownShort1;
|
||||
private readonly ushort unknownShort2;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
private readonly byte[] header3;
|
||||
|
||||
internal readonly byte Category;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header4;
|
||||
|
||||
internal readonly ushort Duty;
|
||||
internal readonly byte DutyType;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
||||
private readonly byte[] header5;
|
||||
|
||||
internal readonly ushort World;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] header6;
|
||||
|
||||
internal readonly byte Objective;
|
||||
internal readonly byte BeginnersWelcome;
|
||||
internal readonly byte Conditions;
|
||||
internal readonly byte DutyFinderSettings;
|
||||
internal readonly byte LootRules;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header7; // all zero in every pf I've examined
|
||||
|
||||
internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted?
|
||||
internal readonly ushort SecondsRemaining;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
|
||||
|
||||
internal readonly ushort MinimumItemLevel;
|
||||
internal readonly ushort HomeWorld;
|
||||
internal readonly ushort CurrentWorld;
|
||||
|
||||
private readonly byte header9;
|
||||
|
||||
internal readonly byte NumSlots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header10;
|
||||
|
||||
internal readonly byte SearchArea;
|
||||
|
||||
private readonly byte header11;
|
||||
|
||||
internal readonly byte NumParties;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly uint[] Slots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly byte[] JobsPresent;
|
||||
|
||||
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
internal readonly byte[] Name;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
|
||||
internal readonly byte[] Description;
|
||||
|
||||
internal bool IsNull()
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header1;
|
||||
internal readonly uint Id;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header2;
|
||||
|
||||
internal readonly uint ContentIdLower;
|
||||
private readonly ushort unknownShort1;
|
||||
private readonly ushort unknownShort2;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
private readonly byte[] header3;
|
||||
|
||||
internal readonly byte Category;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header4;
|
||||
|
||||
internal readonly ushort Duty;
|
||||
internal readonly byte DutyType;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
||||
private readonly byte[] header5;
|
||||
|
||||
internal readonly ushort World;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] header6;
|
||||
|
||||
internal readonly byte Objective;
|
||||
internal readonly byte BeginnersWelcome;
|
||||
internal readonly byte Conditions;
|
||||
internal readonly byte DutyFinderSettings;
|
||||
internal readonly byte LootRules;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header7; // all zero in every pf I've examined
|
||||
|
||||
internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted?
|
||||
internal readonly ushort SecondsRemaining;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
|
||||
|
||||
internal readonly ushort MinimumItemLevel;
|
||||
internal readonly ushort HomeWorld;
|
||||
internal readonly ushort CurrentWorld;
|
||||
|
||||
private readonly byte header9;
|
||||
|
||||
internal readonly byte NumSlots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header10;
|
||||
|
||||
internal readonly byte SearchArea;
|
||||
|
||||
private readonly byte header11;
|
||||
|
||||
internal readonly byte NumParties;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly uint[] Slots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly byte[] JobsPresent;
|
||||
|
||||
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
internal readonly byte[] Name;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
|
||||
internal readonly byte[] Description;
|
||||
|
||||
internal bool IsNull()
|
||||
{
|
||||
// a valid party finder must have at least one slot set
|
||||
return this.Slots.All(slot => slot == 0);
|
||||
}
|
||||
// a valid party finder must have at least one slot set
|
||||
return this.Slots.All(slot => slot == 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder
|
||||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
public class PartyFinderAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||
/// Gets the address of the native ReceiveListing method.
|
||||
/// </summary>
|
||||
public class PartyFinderAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ReceiveListing method.
|
||||
/// </summary>
|
||||
public IntPtr ReceiveListing { get; private set; }
|
||||
public IntPtr ReceiveListing { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,133 +8,132 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder
|
||||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native PartyFinder window.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class PartyFinderGui : IDisposable
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly IntPtr memory;
|
||||
|
||||
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native PartyFinder window.
|
||||
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class PartyFinderGui : IDisposable
|
||||
internal PartyFinderGui()
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly IntPtr memory;
|
||||
this.address = new PartyFinderAddressResolver();
|
||||
this.address.Setup();
|
||||
|
||||
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
internal PartyFinderGui()
|
||||
this.receiveListingHook = new Hook<ReceiveListingDelegate>(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event type fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
/// <param name="listing">The listings received.</param>
|
||||
/// <param name="args">Additional arguments passed by the game.</param>
|
||||
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
public event PartyFinderListingEventDelegate ReceiveListing;
|
||||
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.receiveListingHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of m anaged and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.receiveListingHook.Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
this.address = new PartyFinderAddressResolver();
|
||||
this.address.Setup();
|
||||
Marshal.FreeHGlobal(this.memory);
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
Log.Warning("Could not free PartyFinderGui memory.");
|
||||
}
|
||||
}
|
||||
|
||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||
|
||||
this.receiveListingHook = new Hook<ReceiveListingDelegate>(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HandleListingEvents(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event type fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
/// <param name="listing">The listings received.</param>
|
||||
/// <param name="args">Additional arguments passed by the game.</param>
|
||||
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
||||
this.receiveListingHook.Original(managerPtr, data);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
||||
private void HandleListingEvents(IntPtr data)
|
||||
{
|
||||
var dataPtr = data + 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
public event PartyFinderListingEventDelegate ReceiveListing;
|
||||
var packet = Marshal.PtrToStructure<PartyFinderPacket>(dataPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
// rewriting is an expensive operation, so only do it if necessary
|
||||
var needToRewrite = false;
|
||||
|
||||
for (var i = 0; i < packet.Listings.Length; i++)
|
||||
{
|
||||
this.receiveListingHook.Enable();
|
||||
// these are empty slots that are not shown to the player
|
||||
if (packet.Listings[i].IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var listing = new PartyFinderListing(packet.Listings[i]);
|
||||
var args = new PartyFinderListingEventArgs(packet.BatchNumber);
|
||||
this.ReceiveListing?.Invoke(listing, args);
|
||||
|
||||
if (args.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// hide the listing from the player by setting it to a null listing
|
||||
packet.Listings[i] = default;
|
||||
needToRewrite = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of m anaged and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
if (!needToRewrite)
|
||||
{
|
||||
this.receiveListingHook.Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
Marshal.FreeHGlobal(this.memory);
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
Log.Warning("Could not free PartyFinderGui memory.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
||||
// write our struct into the memory (doing this directly crashes the game)
|
||||
Marshal.StructureToPtr(packet, this.memory, false);
|
||||
|
||||
// copy our new memory over the game's
|
||||
unsafe
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HandleListingEvents(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||
}
|
||||
|
||||
this.receiveListingHook.Original(managerPtr, data);
|
||||
}
|
||||
|
||||
private void HandleListingEvents(IntPtr data)
|
||||
{
|
||||
var dataPtr = data + 0x10;
|
||||
|
||||
var packet = Marshal.PtrToStructure<PartyFinderPacket>(dataPtr);
|
||||
|
||||
// rewriting is an expensive operation, so only do it if necessary
|
||||
var needToRewrite = false;
|
||||
|
||||
for (var i = 0; i < packet.Listings.Length; i++)
|
||||
{
|
||||
// these are empty slots that are not shown to the player
|
||||
if (packet.Listings[i].IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var listing = new PartyFinderListing(packet.Listings[i]);
|
||||
var args = new PartyFinderListingEventArgs(packet.BatchNumber);
|
||||
this.ReceiveListing?.Invoke(listing, args);
|
||||
|
||||
if (args.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// hide the listing from the player by setting it to a null listing
|
||||
packet.Listings[i] = default;
|
||||
needToRewrite = true;
|
||||
}
|
||||
|
||||
if (!needToRewrite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// write our struct into the memory (doing this directly crashes the game)
|
||||
Marshal.StructureToPtr(packet, this.memory, false);
|
||||
|
||||
// copy our new memory over the game's
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize);
|
||||
}
|
||||
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Condition flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ConditionFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// No duty condition.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ConditionFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No duty condition.
|
||||
/// </summary>
|
||||
None = 1,
|
||||
None = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The duty complete condition.
|
||||
/// </summary>
|
||||
DutyComplete = 2,
|
||||
/// <summary>
|
||||
/// The duty complete condition.
|
||||
/// </summary>
|
||||
DutyComplete = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The duty incomplete condition.
|
||||
/// </summary>
|
||||
DutyIncomplete = 4,
|
||||
}
|
||||
/// <summary>
|
||||
/// The duty incomplete condition.
|
||||
/// </summary>
|
||||
DutyIncomplete = 4,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,47 @@
|
|||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Category flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
public enum DutyCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// Category flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// The duty category.
|
||||
/// </summary>
|
||||
public enum DutyCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// The duty category.
|
||||
/// </summary>
|
||||
Duty = 0,
|
||||
Duty = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The quest battle category.
|
||||
/// </summary>
|
||||
QuestBattles = 1 << 0,
|
||||
/// <summary>
|
||||
/// The quest battle category.
|
||||
/// </summary>
|
||||
QuestBattles = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The fate category.
|
||||
/// </summary>
|
||||
Fates = 1 << 1,
|
||||
/// <summary>
|
||||
/// The fate category.
|
||||
/// </summary>
|
||||
Fates = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The treasure hunt category.
|
||||
/// </summary>
|
||||
TreasureHunt = 1 << 2,
|
||||
/// <summary>
|
||||
/// The treasure hunt category.
|
||||
/// </summary>
|
||||
TreasureHunt = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// The hunt category.
|
||||
/// </summary>
|
||||
TheHunt = 1 << 3,
|
||||
/// <summary>
|
||||
/// The hunt category.
|
||||
/// </summary>
|
||||
TheHunt = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// The gathering forays category.
|
||||
/// </summary>
|
||||
GatheringForays = 1 << 4,
|
||||
/// <summary>
|
||||
/// The gathering forays category.
|
||||
/// </summary>
|
||||
GatheringForays = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// The deep dungeons category.
|
||||
/// </summary>
|
||||
DeepDungeons = 1 << 5,
|
||||
/// <summary>
|
||||
/// The deep dungeons category.
|
||||
/// </summary>
|
||||
DeepDungeons = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// The adventuring forays category.
|
||||
/// </summary>
|
||||
AdventuringForays = 1 << 6,
|
||||
}
|
||||
/// <summary>
|
||||
/// The adventuring forays category.
|
||||
/// </summary>
|
||||
AdventuringForays = 1 << 6,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Duty finder settings flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DutyFinderSettingsFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Duty finder settings flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// No duty finder settings.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DutyFinderSettingsFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No duty finder settings.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The undersized party setting.
|
||||
/// </summary>
|
||||
UndersizedParty = 1 << 0,
|
||||
/// <summary>
|
||||
/// The undersized party setting.
|
||||
/// </summary>
|
||||
UndersizedParty = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The minimum item level setting.
|
||||
/// </summary>
|
||||
MinimumItemLevel = 1 << 1,
|
||||
/// <summary>
|
||||
/// The minimum item level setting.
|
||||
/// </summary>
|
||||
MinimumItemLevel = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The silence echo setting.
|
||||
/// </summary>
|
||||
SilenceEcho = 1 << 2,
|
||||
}
|
||||
/// <summary>
|
||||
/// The silence echo setting.
|
||||
/// </summary>
|
||||
SilenceEcho = 1 << 2,
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue