mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
Revert "refactor(Dalamud): switch to file-scoped namespaces"
This reverts commit b5f34c3199.
This commit is contained in:
parent
d473826247
commit
1561fbac00
325 changed files with 45549 additions and 45209 deletions
|
|
@ -1,27 +1,28 @@
|
||||||
namespace Dalamud;
|
namespace Dalamud
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enum describing the language the game loads in.
|
|
||||||
/// </summary>
|
|
||||||
public enum ClientLanguage
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicating a Japanese game client.
|
/// Enum describing the language the game loads in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Japanese,
|
public enum ClientLanguage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicating a Japanese game client.
|
||||||
|
/// </summary>
|
||||||
|
Japanese,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicating an English game client.
|
/// Indicating an English game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
English,
|
English,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicating a German game client.
|
/// Indicating a German game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
German,
|
German,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicating a French game client.
|
/// Indicating a French game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
French,
|
French,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud;
|
namespace Dalamud
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for the <see cref="ClientLanguage"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public static class ClientLanguageExtensions
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a Dalamud ClientLanguage to the corresponding Lumina variant.
|
/// Extension methods for the <see cref="ClientLanguage"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="language">Langauge to convert.</param>
|
public static class ClientLanguageExtensions
|
||||||
/// <returns>Converted langauge.</returns>
|
|
||||||
public static Lumina.Data.Language ToLumina(this ClientLanguage language)
|
|
||||||
{
|
{
|
||||||
return language switch
|
/// <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)
|
||||||
{
|
{
|
||||||
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
return language switch
|
||||||
ClientLanguage.English => Lumina.Data.Language.English,
|
{
|
||||||
ClientLanguage.German => Lumina.Data.Language.German,
|
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
||||||
ClientLanguage.French => Lumina.Data.Language.French,
|
ClientLanguage.English => Lumina.Data.Language.English,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(language)),
|
ClientLanguage.German => Lumina.Data.Language.German,
|
||||||
};
|
ClientLanguage.French => Lumina.Data.Language.French,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(language)),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
namespace Dalamud.Configuration;
|
namespace Dalamud.Configuration
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configuration to store settings for a dalamud plugin.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPluginConfiguration
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets configuration version.
|
/// Configuration to store settings for a dalamud plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int Version { get; set; }
|
public interface IPluginConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets configuration version.
|
||||||
|
/// </summary>
|
||||||
|
int Version { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,267 +8,268 @@ using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Dalamud.Configuration.Internal;
|
namespace Dalamud.Configuration.Internal
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class containing Dalamud settings.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable]
|
|
||||||
internal sealed class DalamudConfiguration
|
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerSettings SerializerSettings = new()
|
/// <summary>
|
||||||
|
/// Class containing Dalamud settings.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal sealed class DalamudConfiguration
|
||||||
{
|
{
|
||||||
TypeNameHandling = TypeNameHandling.All,
|
private static readonly JsonSerializerSettings SerializerSettings = new()
|
||||||
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
|
|
||||||
{
|
{
|
||||||
deserialized = JsonConvert.DeserializeObject<DalamudConfiguration>(File.ReadAllText(path), SerializerSettings);
|
TypeNameHandling = TypeNameHandling.All,
|
||||||
}
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
catch (Exception ex)
|
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)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path);
|
DalamudConfiguration deserialized;
|
||||||
deserialized = new DalamudConfiguration();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialized.configPath = path;
|
/// <summary>
|
||||||
|
/// Save the configuration at the path it was loaded from.
|
||||||
return deserialized;
|
/// </summary>
|
||||||
}
|
public void Save()
|
||||||
|
{
|
||||||
/// <summary>
|
File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
||||||
/// Save the configuration at the path it was loaded from.
|
this.DalamudConfigurationSaved?.Invoke(this);
|
||||||
/// </summary>
|
}
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
|
||||||
this.DalamudConfigurationSaved?.Invoke(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
namespace Dalamud.Configuration.Internal;
|
namespace Dalamud.Configuration
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Additional locations to load dev plugins from.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class DevPluginLocationSettings
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the dev pluign path.
|
/// Additional locations to load dev plugins from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Path { get; set; }
|
internal sealed class DevPluginLocationSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the dev pluign path.
|
||||||
|
/// </summary>
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEnabled { get; set; }
|
public bool IsEnabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clone this object.
|
/// Clone this object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A shallow copy of this object.</returns>
|
/// <returns>A shallow copy of this object.</returns>
|
||||||
public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
|
public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
namespace Dalamud.Configuration.Internal;
|
namespace Dalamud.Configuration.Internal
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Settings for DevPlugins.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class DevPluginSettings
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
|
/// Settings for DevPlugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool StartOnBoot { get; set; } = true;
|
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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutomaticReloading { get; set; } = false;
|
public bool AutomaticReloading { get; set; } = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,38 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Configuration.Internal;
|
namespace Dalamud.Configuration.Internal
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Environmental configuration settings.
|
|
||||||
/// </summary>
|
|
||||||
internal class EnvironmentConfiguration
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
|
/// Environmental configuration settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
|
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");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
|
public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
|
/// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
|
public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
|
/// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing.
|
/// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER");
|
public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER");
|
||||||
|
|
||||||
private static bool GetEnvironmentVariable(string name)
|
private static bool GetEnvironmentVariable(string name)
|
||||||
=> bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
|
=> bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,29 @@
|
||||||
namespace Dalamud.Configuration.Internal;
|
namespace Dalamud.Configuration
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Third party repository for dalamud plugins.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class ThirdPartyRepoSettings
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the third party repo url.
|
/// Third party repository for dalamud plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Url { get; set; }
|
internal sealed class ThirdPartyRepoSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the third party repo url.
|
||||||
|
/// </summary>
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEnabled { get; set; }
|
public bool IsEnabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a short name for the repo url.
|
/// Gets or sets a short name for the repo url.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clone this object.
|
/// Clone this object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A shallow copy of this object.</returns>
|
/// <returns>A shallow copy of this object.</returns>
|
||||||
public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
|
public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,128 +2,129 @@ using System.IO;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginConfigurations"/> class.
|
/// Configuration to store settings for a dalamud plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="storageFolder">Directory for storage of plugin configuration files.</param>
|
public sealed class PluginConfigurations
|
||||||
public PluginConfigurations(string storageFolder)
|
|
||||||
{
|
{
|
||||||
this.configDirectory = new DirectoryInfo(storageFolder);
|
private readonly DirectoryInfo configDirectory;
|
||||||
this.configDirectory.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save/Load plugin configuration.
|
/// Initializes a new instance of the <see cref="PluginConfigurations"/> class.
|
||||||
/// NOTE: Save/Load are still using Type information for now,
|
/// </summary>
|
||||||
/// despite LoadForType superseding Load and not requiring or using it.
|
/// <param name="storageFolder">Directory for storage of plugin configuration files.</param>
|
||||||
/// It might be worth removing the Type info from Save, to strip it from all future saved configs,
|
public PluginConfigurations(string storageFolder)
|
||||||
/// 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
|
|
||||||
{
|
{
|
||||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
this.configDirectory = new DirectoryInfo(storageFolder);
|
||||||
TypeNameHandling = TypeNameHandling.Objects,
|
this.configDirectory.Create();
|
||||||
}));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load plugin configuration.
|
/// Save/Load plugin configuration.
|
||||||
/// </summary>
|
/// NOTE: Save/Load are still using Type information for now,
|
||||||
/// <param name="pluginName">Plugin name.</param>
|
/// despite LoadForType superseding Load and not requiring or using it.
|
||||||
/// <returns>Plugin configuration.</returns>
|
/// It might be worth removing the Type info from Save, to strip it from all future saved configs,
|
||||||
public IPluginConfiguration? Load(string pluginName)
|
/// and then Load() can probably be removed entirely.
|
||||||
{
|
/// </summary>
|
||||||
var path = this.GetConfigFile(pluginName);
|
/// <param name="config">Plugin configuration.</param>
|
||||||
|
/// <param name="pluginName">Plugin name.</param>
|
||||||
if (!path.Exists)
|
public void Save(IPluginConfiguration config, string pluginName)
|
||||||
return null;
|
{
|
||||||
|
File.WriteAllText(this.GetConfigFile(pluginName).FullName, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
|
||||||
return JsonConvert.DeserializeObject<IPluginConfiguration>(
|
|
||||||
File.ReadAllText(path.FullName),
|
|
||||||
new JsonSerializerSettings
|
|
||||||
{
|
{
|
||||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
TypeNameHandling = TypeNameHandling.Objects,
|
TypeNameHandling = TypeNameHandling.Objects,
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete the configuration file and folder for the specified plugin.
|
/// Load plugin configuration.
|
||||||
/// This will throw an <see cref="IOException"/> if the plugin did not correctly close its handles.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="pluginName">Plugin name.</param>
|
||||||
/// <param name="pluginName">The name of the plugin.</param>
|
/// <returns>Plugin configuration.</returns>
|
||||||
public void Delete(string pluginName)
|
public IPluginConfiguration? Load(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);
|
var path = this.GetConfigFile(pluginName);
|
||||||
|
|
||||||
if (!path.Exists)
|
if (!path.Exists)
|
||||||
{
|
return null;
|
||||||
path.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.FullName;
|
return JsonConvert.DeserializeObject<IPluginConfiguration>(
|
||||||
|
File.ReadAllText(path.FullName),
|
||||||
|
new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
|
TypeNameHandling = TypeNameHandling.Objects,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
|
/// <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)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
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>
|
|
||||||
/// 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,378 +33,379 @@ using Serilog.Events;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Dalamud.Test")]
|
[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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
/// The main Dalamud class containing all subsystems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info">DalamudStartInfo instance.</param>
|
internal sealed class Dalamud : IDisposable
|
||||||
/// <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.ApplyProcessPatch();
|
#region Internals
|
||||||
|
|
||||||
Service<Dalamud>.Set(this);
|
private readonly ManualResetEvent unloadSignal;
|
||||||
Service<DalamudStartInfo>.Set(info);
|
private readonly ManualResetEvent finishUnloadSignal;
|
||||||
Service<DalamudConfiguration>.Set(configuration);
|
private MonoMod.RuntimeDetour.Hook processMonoHook;
|
||||||
|
private bool hasDisposedPlugins = false;
|
||||||
|
|
||||||
this.LogLevelSwitch = loggingLevelSwitch;
|
#endregion
|
||||||
|
|
||||||
this.unloadSignal = new ManualResetEvent(false);
|
/// <summary>
|
||||||
this.unloadSignal.Reset();
|
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
||||||
|
/// </summary>
|
||||||
this.finishUnloadSignal = finishSignal;
|
/// <param name="info">DalamudStartInfo instance.</param>
|
||||||
this.finishUnloadSignal.Reset();
|
/// <param name="loggingLevelSwitch">LoggingLevelSwitch to control Serilog level.</param>
|
||||||
}
|
/// <param name="finishSignal">Signal signalling shutdown.</param>
|
||||||
|
/// <param name="configuration">The Dalamud configuration.</param>
|
||||||
/// <summary>
|
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration)
|
||||||
/// 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;
|
this.ApplyProcessPatch();
|
||||||
|
|
||||||
Service<ServiceContainer>.Set();
|
Service<Dalamud>.Set(this);
|
||||||
|
Service<DalamudStartInfo>.Set(info);
|
||||||
|
Service<DalamudConfiguration>.Set(configuration);
|
||||||
|
|
||||||
// Initialize the process information.
|
this.LogLevelSwitch = loggingLevelSwitch;
|
||||||
Service<SigScanner>.Set(new SigScanner(true));
|
|
||||||
Service<HookManager>.Set();
|
|
||||||
|
|
||||||
// Initialize FFXIVClientStructs function resolver
|
this.unloadSignal = new ManualResetEvent(false);
|
||||||
FFXIVClientStructs.Resolver.Initialize();
|
this.unloadSignal.Reset();
|
||||||
Log.Information("[T1] FFXIVClientStructs initialized!");
|
|
||||||
|
|
||||||
// Initialize game subsystem
|
this.finishUnloadSignal = finishSignal;
|
||||||
var framework = Service<Framework>.Set();
|
this.finishUnloadSignal.Reset();
|
||||||
Log.Information("[T1] Framework OK!");
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
Service<TaskTracker>.Set();
|
|
||||||
Log.Information("[T1] TaskTracker OK!");
|
|
||||||
#endif
|
|
||||||
Service<GameNetwork>.Set();
|
|
||||||
Service<GameGui>.Set();
|
|
||||||
|
|
||||||
framework.Enable();
|
|
||||||
|
|
||||||
Log.Information("[T1] Load complete!");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Tier 1 load failed.");
|
|
||||||
this.Unload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs tier 2 of the Dalamud initialization process.
|
/// Gets LoggingLevelSwitch for Dalamud and Plugin logs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Whether or not the load succeeded.</returns>
|
internal LoggingLevelSwitch LogLevelSwitch { get; private set; }
|
||||||
public bool LoadTier2()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
var antiDebug = Service<AntiDebug>.Set();
|
/// <summary>
|
||||||
if (!antiDebug.IsEnabled)
|
/// 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!");
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
antiDebug.Enable();
|
Service<TaskTracker>.Set();
|
||||||
|
Log.Information("[T1] TaskTracker OK!");
|
||||||
|
#endif
|
||||||
|
Service<GameNetwork>.Set();
|
||||||
|
Service<GameGui>.Set();
|
||||||
|
|
||||||
|
framework.Enable();
|
||||||
|
|
||||||
|
Log.Information("[T1] Load complete!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Tier 1 load failed.");
|
||||||
|
this.Unload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
#else
|
#else
|
||||||
if (configuration.IsAntiAntiDebugEnabled)
|
if (configuration.IsAntiAntiDebugEnabled)
|
||||||
antiDebug.Enable();
|
antiDebug.Enable();
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("[T2] AntiDebug OK!");
|
||||||
|
|
||||||
|
Service<WinSockHandlers>.Set();
|
||||||
|
Log.Information("[T2] WinSock 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("[T2] Data 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("[T2] LOC OK!");
|
||||||
|
|
||||||
|
// This is enabled in ImGuiScene setup
|
||||||
|
Service<DalamudIME>.Set();
|
||||||
|
Log.Information("[T2] IME OK!");
|
||||||
|
|
||||||
|
Service<InterfaceManager>.Set().Enable();
|
||||||
|
Log.Information("[T2] IM OK!");
|
||||||
|
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
Service<SeStringManager>.Set();
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
|
Log.Information("[T2] SeString OK!");
|
||||||
|
|
||||||
|
// Initialize managers. Basically handlers for the logic
|
||||||
|
Service<CommandManager>.Set();
|
||||||
|
|
||||||
|
Service<DalamudCommands>.Set().SetupCommands();
|
||||||
|
|
||||||
|
Log.Information("[T2] CM OK!");
|
||||||
|
|
||||||
|
Service<ChatHandlers>.Set();
|
||||||
|
|
||||||
|
Log.Information("[T2] CH OK!");
|
||||||
|
|
||||||
|
clientState.Enable();
|
||||||
|
Log.Information("[T2] CS ENABLE!");
|
||||||
|
|
||||||
|
Service<DalamudAtkTweaks>.Set().Enable();
|
||||||
|
|
||||||
|
Log.Information("[T2] Load complete!");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
Log.Information("[T2] AntiDebug OK!");
|
|
||||||
|
|
||||||
Service<WinSockHandlers>.Set();
|
|
||||||
Log.Information("[T2] WinSock OK!");
|
|
||||||
|
|
||||||
Service<NetworkHandlers>.Set();
|
|
||||||
Log.Information("[T2] NH OK!");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Service<DataManager>.Set().Initialize(this.AssetDirectory.FullName);
|
Log.Error(ex, "Tier 2 load failed.");
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Could not initialize DataManager.");
|
|
||||||
this.Unload();
|
this.Unload();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("[T2] Data OK!");
|
return true;
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Information("[T2] LOC OK!");
|
|
||||||
|
|
||||||
// This is enabled in ImGuiScene setup
|
|
||||||
Service<DalamudIME>.Set();
|
|
||||||
Log.Information("[T2] IME OK!");
|
|
||||||
|
|
||||||
Service<InterfaceManager>.Set().Enable();
|
|
||||||
Log.Information("[T2] IM OK!");
|
|
||||||
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
Service<SeStringManager>.Set();
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
|
|
||||||
Log.Information("[T2] SeString OK!");
|
|
||||||
|
|
||||||
// Initialize managers. Basically handlers for the logic
|
|
||||||
Service<CommandManager>.Set();
|
|
||||||
|
|
||||||
Service<DalamudCommands>.Set().SetupCommands();
|
|
||||||
|
|
||||||
Log.Information("[T2] CM OK!");
|
|
||||||
|
|
||||||
Service<ChatHandlers>.Set();
|
|
||||||
|
|
||||||
Log.Information("[T2] CH OK!");
|
|
||||||
|
|
||||||
clientState.Enable();
|
|
||||||
Log.Information("[T2] CS 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;
|
/// <summary>
|
||||||
}
|
/// Runs tier 3 of the Dalamud initialization process.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <returns>Whether or not the load succeeded.</returns>
|
||||||
/// Runs tier 3 of the Dalamud initialization process.
|
public bool LoadTier3()
|
||||||
/// </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
|
try
|
||||||
{
|
{
|
||||||
_ = pluginManager.SetPluginReposFromConfigAsync(false);
|
Log.Information("[T3] START!");
|
||||||
|
|
||||||
pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting;
|
var pluginManager = Service<PluginManager>.Set();
|
||||||
|
Service<CallGate>.Set();
|
||||||
|
|
||||||
Log.Information("[T3] PM OK!");
|
try
|
||||||
|
{
|
||||||
|
_ = pluginManager.SetPluginReposFromConfigAsync(false);
|
||||||
|
|
||||||
pluginManager.CleanupPlugins();
|
pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting;
|
||||||
Log.Information("[T3] PMC OK!");
|
|
||||||
|
|
||||||
pluginManager.LoadAllPlugins();
|
Log.Information("[T3] PM OK!");
|
||||||
Log.Information("[T3] PML 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.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Plugin load failed.");
|
Log.Error(ex, "Tier 3 load failed.");
|
||||||
|
this.Unload();
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service<DalamudInterface>.Set();
|
return true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
/// <summary>
|
||||||
}
|
/// Queue an unload of Dalamud when it gets the chance.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
public void Unload()
|
||||||
/// 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
|
|
||||||
{
|
{
|
||||||
if (!this.hasDisposedPlugins)
|
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
|
||||||
{
|
{
|
||||||
this.DisposePlugins();
|
if (!this.hasDisposedPlugins)
|
||||||
Thread.Sleep(100);
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service<Framework>.GetNullable()?.Dispose();
|
// Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}");
|
||||||
Service<ClientState>.GetNullable()?.Dispose();
|
return result;
|
||||||
|
|
||||||
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)
|
|
||||||
|
private void ApplyProcessPatch()
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Dalamud::Dispose() failed.");
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,57 +3,58 @@ using System;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud;
|
namespace Dalamud
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Struct containing information needed to initialize Dalamud.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable]
|
|
||||||
public record DalamudStartInfo
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the working directory of the XIVLauncher installations.
|
/// Struct containing information needed to initialize Dalamud.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string WorkingDirectory { get; set; }
|
[Serializable]
|
||||||
|
public record DalamudStartInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the working directory of the XIVLauncher installations.
|
||||||
|
/// </summary>
|
||||||
|
public string WorkingDirectory { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the configuration file.
|
/// Gets the path to the configuration file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ConfigurationPath { get; init; }
|
public string ConfigurationPath { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the directory for installed plugins.
|
/// Gets the path to the directory for installed plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PluginDirectory { get; init; }
|
public string PluginDirectory { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the directory for developer plugins.
|
/// Gets the path to the directory for developer plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DefaultPluginDirectory { get; init; }
|
public string DefaultPluginDirectory { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to core Dalamud assets.
|
/// Gets the path to core Dalamud assets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AssetDirectory { get; init; }
|
public string AssetDirectory { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the language of the game client.
|
/// Gets the language of the game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClientLanguage Language { get; init; }
|
public ClientLanguage Language { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current game version code.
|
/// Gets the current game version code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonConverter(typeof(GameVersionConverter))]
|
[JsonConverter(typeof(GameVersionConverter))]
|
||||||
public GameVersion GameVersion { get; init; }
|
public GameVersion GameVersion { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not market board information should be uploaded by default.
|
/// Gets a value indicating whether or not market board information should be uploaded by default.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool OptOutMbCollection { get; init; }
|
public bool OptOutMbCollection { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value that specifies how much to wait before a new Dalamud session.
|
/// Gets a value that specifies how much to wait before a new Dalamud session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DelayInitializeMs { get; init; } = 0;
|
public int DelayInitializeMs { get; init; } = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,329 +18,330 @@ using Lumina.Excel;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DataManager"/> class.
|
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal DataManager()
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed class DataManager : IDisposable
|
||||||
{
|
{
|
||||||
this.Language = Service<DalamudStartInfo>.Get().Language;
|
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||||
|
|
||||||
// Set up default values so plugins do not null-reference when data is being loaded.
|
private Thread luminaResourceThread;
|
||||||
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
|
private CancellationTokenSource luminaCancellationTokenSource;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current game client language.
|
/// Initializes a new instance of the <see cref="DataManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClientLanguage Language { get; private set; }
|
internal DataManager()
|
||||||
|
|
||||||
/// <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
|
|
||||||
{
|
{
|
||||||
ClientLanguage.Japanese => "ja/",
|
this.Language = Service<DalamudStartInfo>.Get().Language;
|
||||||
ClientLanguage.English => "en/",
|
|
||||||
ClientLanguage.German => "de/",
|
|
||||||
ClientLanguage.French => "fr/",
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.GetIcon(type, iconId);
|
// 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>
|
/// <summary>
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given type.
|
/// Gets the current game client language.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
public ClientLanguage Language { get; private set; }
|
||||||
/// <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);
|
/// <summary>
|
||||||
var file = this.GetFile<TexFile>(filePath);
|
/// Gets the OpCodes sent by the server to the client.
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
||||||
|
|
||||||
if (type == string.Empty || file != default)
|
/// <summary>
|
||||||
return file;
|
/// Gets the OpCodes sent by the client to the server.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
|
||||||
|
|
||||||
// Couldn't get specific type, try for generic version.
|
/// <summary>
|
||||||
filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
|
/// Gets a <see cref="Lumina"/> object which gives access to any excel/game data.
|
||||||
file = this.GetFile<TexFile>(filePath);
|
/// </summary>
|
||||||
return file;
|
public GameData GameData { get; private set; }
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a <see cref="TexFile"/> containing the HQ icon with the given ID.
|
/// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="iconId">The icon ID.</param>
|
public ExcelModule Excel => this.GameData?.Excel;
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
public TexFile? GetHqIcon(uint iconId)
|
|
||||||
=> this.GetIcon(true, iconId);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the passed <see cref="TexFile"/> as a drawable ImGui TextureWrap.
|
/// Gets a value indicating whether Game Data is ready to be read.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
public bool IsDataReady { get; private set; }
|
||||||
/// <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>
|
#region Lumina Wrappers
|
||||||
/// 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>
|
/// <summary>
|
||||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID.
|
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="iconId">The icon ID.</param>
|
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
||||||
=> 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
|
|
||||||
{
|
{
|
||||||
Log.Verbose("Starting data load...");
|
return this.Excel.GetSheet<T>();
|
||||||
|
}
|
||||||
|
|
||||||
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
/// <summary>
|
||||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
|
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type with a specified language.
|
||||||
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
|
/// </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());
|
||||||
|
}
|
||||||
|
|
||||||
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
|
/// <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);
|
||||||
|
}
|
||||||
|
|
||||||
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
/// <summary>
|
||||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
|
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
||||||
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
|
/// </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;
|
||||||
|
}
|
||||||
|
|
||||||
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
|
/// <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);
|
||||||
|
}
|
||||||
|
|
||||||
var luminaOptions = new LuminaOptions
|
/// <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
|
||||||
{
|
{
|
||||||
CacheFileResources = true,
|
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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
{
|
||||||
|
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,
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
PanicOnSheetChecksumMismatch = true,
|
PanicOnSheetChecksumMismatch = true,
|
||||||
#else
|
#else
|
||||||
PanicOnSheetChecksumMismatch = false,
|
PanicOnSheetChecksumMismatch = false,
|
||||||
#endif
|
#endif
|
||||||
DefaultExcelLanguage = this.Language.ToLumina(),
|
DefaultExcelLanguage = this.Language.ToLumina(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var processModule = Process.GetCurrentProcess().MainModule;
|
var processModule = Process.GetCurrentProcess().MainModule;
|
||||||
if (processModule != null)
|
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 = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions);
|
||||||
{
|
|
||||||
this.GameData.ProcessFileHandleQueue();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Thread.Sleep(5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
this.luminaResourceThread.Start();
|
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
this.IsDataReady = true;
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not download data.");
|
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,261 +21,262 @@ using Serilog.Events;
|
||||||
|
|
||||||
using static Dalamud.NativeFunctions;
|
using static Dalamud.NativeFunctions;
|
||||||
|
|
||||||
namespace Dalamud;
|
namespace Dalamud
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The main entrypoint for the Dalamud system.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class EntryPoint
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
/// The main entrypoint for the Dalamud system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
public sealed class EntryPoint
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var infoStr = Marshal.PtrToStringUTF8(infoPtr);
|
/// <summary>
|
||||||
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr);
|
/// 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);
|
||||||
|
|
||||||
new Thread(() => RunThread(info)).Start();
|
/// <summary>
|
||||||
}
|
/// Initialize Dalamud.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||||
/// Initialize all Dalamud subsystems and start running on the main thread.
|
public static void Initialize(IntPtr infoPtr)
|
||||||
/// </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)
|
var infoStr = Marshal.PtrToStringUTF8(infoPtr);
|
||||||
{
|
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr);
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
new Thread(() => RunThread(info)).Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup logger
|
/// <summary>
|
||||||
var levelSwitch = InitLogging(info.WorkingDirectory);
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load configuration first to get some early persistent state, like log level
|
// Setup logger
|
||||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath);
|
var levelSwitch = InitLogging(info.WorkingDirectory);
|
||||||
|
|
||||||
// Set the appropriate logging level from the configuration
|
// Load configuration first to get some early persistent state, like log level
|
||||||
|
var configuration = DalamudConfiguration.Load(info.ConfigurationPath);
|
||||||
|
|
||||||
|
// Set the appropriate logging level from the configuration
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
levelSwitch.MinimumLevel = configuration.LogLevel;
|
levelSwitch.MinimumLevel = configuration.LogLevel;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Log any unhandled exception.
|
// Log any unhandled exception.
|
||||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||||
|
|
||||||
var finishSignal = new ManualResetEvent(false);
|
var finishSignal = new ManualResetEvent(false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
|
||||||
if (info.DelayInitializeMs > 0)
|
|
||||||
{
|
{
|
||||||
Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs));
|
if (info.DelayInitializeMs > 0)
|
||||||
Thread.Sleep(info.DelayInitializeMs);
|
{
|
||||||
|
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(new string('-', 80));
|
Log.Information("Session has ended.");
|
||||||
Log.Information("Initializing a session..");
|
Log.CloseAndFlush();
|
||||||
|
|
||||||
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
finishSignal.Set();
|
||||||
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)
|
|
||||||
|
private static void InitSymbolHandler(DalamudStartInfo info)
|
||||||
{
|
{
|
||||||
Log.Fatal(ex, "Unhandled exception on main thread.");
|
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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
private static LoggingLevelSwitch InitLogging(string baseDirectory)
|
||||||
{
|
{
|
||||||
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
|
#if DEBUG
|
||||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||||
var oldPath = Path.Combine(baseDirectory, "dalamud.log.old");
|
var oldPath = Path.Combine(baseDirectory, "dalamud.log.old");
|
||||||
#else
|
#else
|
||||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
|
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
|
||||||
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
|
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CullLogFile(logPath, oldPath, 1 * 1024 * 1024);
|
CullLogFile(logPath, oldPath, 1 * 1024 * 1024);
|
||||||
CullLogFile(oldPath, null, 10 * 1024 * 1024);
|
CullLogFile(oldPath, null, 10 * 1024 * 1024);
|
||||||
|
|
||||||
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.WriteTo.Async(a => a.File(logPath))
|
.WriteTo.Async(a => a.File(logPath))
|
||||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||||
.MinimumLevel.ControlledBy(levelSwitch)
|
.MinimumLevel.ControlledBy(levelSwitch)
|
||||||
.CreateLogger();
|
.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
|
|
||||||
{
|
{
|
||||||
var bufferSize = 4096;
|
try
|
||||||
|
|
||||||
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 oldFile = new FileInfo(oldPath);
|
var bufferSize = 4096;
|
||||||
|
|
||||||
if (!oldFile.Exists)
|
var logFile = new FileInfo(logPath);
|
||||||
oldFile.Create().Close();
|
|
||||||
|
|
||||||
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
if (!logFile.Exists)
|
||||||
using var writer = new BinaryWriter(oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite));
|
logFile.Create();
|
||||||
|
|
||||||
var read = -1;
|
if (logFile.Length <= cullingFileSize)
|
||||||
var total = 0;
|
return;
|
||||||
var buffer = new byte[bufferSize];
|
|
||||||
while (read != 0 && total < amountToCull)
|
var amountToCull = logFile.Length - cullingFileSize;
|
||||||
|
|
||||||
|
if (amountToCull < bufferSize)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (oldPath != null)
|
||||||
{
|
{
|
||||||
read = reader.Read(buffer, 0, buffer.Length);
|
var oldFile = new FileInfo(oldPath);
|
||||||
writer.Write(buffer, 0, read);
|
|
||||||
total += read;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
Log.Error(ex, "Log cull failed");
|
||||||
using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite));
|
|
||||||
|
|
||||||
reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin);
|
/*
|
||||||
|
var caption = "XIVLauncher Error";
|
||||||
var read = -1;
|
var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
|
||||||
var total = 0;
|
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Log cull failed");
|
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";
|
||||||
var caption = "XIVLauncher Error";
|
if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null)
|
||||||
var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
|
{
|
||||||
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
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 OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
|
||||||
{
|
|
||||||
switch (args.ExceptionObject)
|
|
||||||
{
|
{
|
||||||
case Exception ex:
|
if (!args.Observed)
|
||||||
Log.Fatal(ex, "Unhandled exception on AppDomain");
|
Log.Error(args.Exception, "Unobserved exception in Task.");
|
||||||
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,111 +3,112 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base memory address resolver.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class BaseAddressResolver
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of memory addresses that were found, to list in /xldata.
|
/// Base memory address resolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
public abstract class BaseAddressResolver
|
||||||
|
|
||||||
/// <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()
|
|
||||||
{
|
{
|
||||||
var scanner = Service<SigScanner>.Get();
|
/// <summary>
|
||||||
this.Setup(scanner);
|
/// 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();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
protected bool IsResolved { get; set; }
|
||||||
public void Setup(SigScanner scanner)
|
|
||||||
{
|
|
||||||
// Because C# don't allow to call virtual function while in ctor
|
|
||||||
// we have to do this shit :\
|
|
||||||
|
|
||||||
if (this.IsResolved)
|
/// <summary>
|
||||||
|
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||||
|
/// </summary>
|
||||||
|
public void Setup()
|
||||||
{
|
{
|
||||||
return;
|
var scanner = Service<SigScanner>.Get();
|
||||||
|
this.Setup(scanner);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scanner.Is32BitProcess)
|
/// <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)
|
||||||
{
|
{
|
||||||
this.Setup32Bit(scanner);
|
// Because C# don't allow to call virtual function while in ctor
|
||||||
}
|
// we have to do this shit :\
|
||||||
else
|
|
||||||
{
|
if (this.IsResolved)
|
||||||
this.Setup64Bit(scanner);
|
{
|
||||||
|
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.SetupInternal(scanner);
|
/// <summary>
|
||||||
|
/// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
|
||||||
var className = this.GetType().Name;
|
/// </summary>
|
||||||
DebugScannedValues[className] = new List<(string, IntPtr)>();
|
/// <typeparam name="T">The delegate to marshal the function pointer to.</typeparam>
|
||||||
|
/// <param name="address">The address of the virtual table.</param>
|
||||||
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
/// <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
|
||||||
{
|
{
|
||||||
DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this)));
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.IsResolved = true;
|
/// <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>
|
/// <summary>
|
||||||
/// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The delegate to marshal the function pointer to.</typeparam>
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
/// <param name="address">The address of the virtual table.</param>
|
protected virtual void Setup64Bit(SigScanner scanner)
|
||||||
/// <param name="vtableOffset">The offset from address to the vtable pointer.</param>
|
{
|
||||||
/// <param name="count">The vfunc index.</param>
|
throw new NotSupportedException("64 bit version is not supported.");
|
||||||
/// <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);
|
|
||||||
|
|
||||||
// Get an address to the function
|
/// <summary>
|
||||||
var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
|
/// </summary>
|
||||||
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
}
|
protected virtual void SetupInternal(SigScanner scanner)
|
||||||
|
{
|
||||||
/// <summary>
|
// Do nothing
|
||||||
/// 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 Dalamud.Utility;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chat events and public helper functions.
|
|
||||||
/// </summary>
|
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
public class ChatHandlers
|
|
||||||
{
|
{
|
||||||
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
/// <summary>
|
||||||
// {
|
/// Chat events and public helper functions.
|
||||||
// { "", "<:ffxive071:585847382210642069>" },
|
/// </summary>
|
||||||
// { "", "<:ffxive083:585848592699490329>" },
|
[PluginInterface]
|
||||||
// };
|
[InterfaceVersion("1.0")]
|
||||||
|
public class ChatHandlers
|
||||||
// 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>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<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),
|
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 (?:.+) 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),
|
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
ClientLanguage.French,
|
|
||||||
new Regex[]
|
|
||||||
{
|
{
|
||||||
|
ClientLanguage.French,
|
||||||
|
new Regex[]
|
||||||
|
{
|
||||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
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 Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||||
|
|
||||||
private readonly DalamudLinkPayload openInstallerWindowLink;
|
private readonly DalamudLinkPayload openInstallerWindowLink;
|
||||||
|
|
||||||
private bool hasSeenLoadingMsg;
|
private bool hasSeenLoadingMsg;
|
||||||
private bool hasAutoUpdatedPlugins;
|
private bool hasAutoUpdatedPlugins;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
|
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ChatHandlers()
|
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();
|
var chatGui = Service<ChatGui>.Get();
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||||
/// Gets the last URL seen in chat.
|
chatGui.ChatMessage += this.OnChatMessage;
|
||||||
/// </summary>
|
|
||||||
public string? LastLink { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||||
/// 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
|
Service<DalamudInterface>.Get().OpenPluginInstaller();
|
||||||
Log.Debug("Handled RMT ad: " + message.TextValue);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)))
|
||||||
|
{
|
||||||
|
// This seems to be in the user block list - let's not show it
|
||||||
|
Log.Debug("Blocklist triggered");
|
||||||
isHandled = true;
|
isHandled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.BadWords != null &&
|
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
|
||||||
{
|
{
|
||||||
// This seems to be in the user block list - let's not show it
|
var startInfo = Service<DalamudStartInfo>.Get();
|
||||||
Log.Debug("Blocklist triggered");
|
var clientState = Service<ClientState.ClientState>.Get();
|
||||||
isHandled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
||||||
{
|
this.PrintWelcomeMessage();
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
|
||||||
var clientState = Service<ClientState.ClientState>.Get();
|
|
||||||
|
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
// For injections while logged in
|
||||||
this.PrintWelcomeMessage();
|
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||||
|
this.PrintWelcomeMessage();
|
||||||
|
|
||||||
// For injections while logged in
|
if (!this.hasAutoUpdatedPlugins)
|
||||||
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
this.AutoUpdatePlugins();
|
||||||
this.PrintWelcomeMessage();
|
|
||||||
|
|
||||||
if (!this.hasAutoUpdatedPlugins)
|
|
||||||
this.AutoUpdatePlugins();
|
|
||||||
|
|
||||||
#if !DEBUG && false
|
#if !DEBUG && false
|
||||||
if (!this.hasSeenLoadingMsg)
|
if (!this.hasSeenLoadingMsg)
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (type == XivChatType.RetainerSale)
|
if (type == XivChatType.RetainerSale)
|
||||||
{
|
|
||||||
foreach (var regex in this.retainerSaleRegexes[startInfo.Language])
|
|
||||||
{
|
{
|
||||||
var matchInfo = regex.Match(message.TextValue);
|
foreach (var regex in this.retainerSaleRegexes[startInfo.Language])
|
||||||
|
|
||||||
// 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()));
|
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));
|
||||||
break;
|
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 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))
|
|
||||||
{
|
|
||||||
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;
|
var messageCopy = message;
|
||||||
configuration.Save();
|
var senderCopy = sender;
|
||||||
|
|
||||||
|
var linkMatch = this.urlRegex.Match(message.TextValue);
|
||||||
|
if (linkMatch.Value.Length > 0)
|
||||||
|
this.LastLink = linkMatch.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasSeenLoadingMsg = true;
|
private void PrintWelcomeMessage()
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
var chatGui = Service<ChatGui>.Get();
|
||||||
return;
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
}
|
var pluginManager = Service<PluginManager>.Get();
|
||||||
|
var dalamudInterface = Service<DalamudInterface>.Get();
|
||||||
|
|
||||||
this.hasAutoUpdatedPlugins = true;
|
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
||||||
|
|
||||||
Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task =>
|
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 (task.IsFaulted)
|
|
||||||
|
if (configuration.PrintPluginsWelcomeMsg)
|
||||||
{
|
{
|
||||||
Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedPlugins = task.Result;
|
this.hasAutoUpdatedPlugins = true;
|
||||||
if (updatedPlugins != null && updatedPlugins.Any())
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
|
|
||||||
chatGui.PrintChat(new XivChatEntry
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedPlugins = task.Result;
|
||||||
|
if (updatedPlugins != null && updatedPlugins.Any())
|
||||||
|
{
|
||||||
|
if (configuration.AutoUpdatePlugins)
|
||||||
{
|
{
|
||||||
Message = new SeString(new List<Payload>()
|
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();
|
||||||
|
|
||||||
|
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(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 TextPayload(" ["),
|
||||||
new UIForegroundPayload(500),
|
new UIForegroundPayload(500),
|
||||||
|
|
@ -321,11 +321,12 @@ public class ChatHandlers
|
||||||
RawPayload.LinkTerminator,
|
RawPayload.LinkTerminator,
|
||||||
new UIForegroundPayload(0),
|
new UIForegroundPayload(0),
|
||||||
new TextPayload("]"),
|
new TextPayload("]"),
|
||||||
}),
|
}),
|
||||||
Type = XivChatType.Urgent,
|
Type = XivChatType.Urgent,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,179 +7,180 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BuddyList"/> class.
|
/// This collection represents the buddies present in your squadron or trust party.
|
||||||
|
/// It does not include the local player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">Client state address resolver.</param>
|
[PluginInterface]
|
||||||
internal BuddyList(ClientStateAddressResolver addressResolver)
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed partial class BuddyList
|
||||||
{
|
{
|
||||||
this.address = addressResolver;
|
private const uint InvalidObjectID = 0xE0000000;
|
||||||
|
|
||||||
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
|
private readonly ClientStateAddressResolver address;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of battle buddies the local player has.
|
/// Initializes a new instance of the <see cref="BuddyList"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Length
|
/// <param name="addressResolver">Client state address resolver.</param>
|
||||||
{
|
internal BuddyList(ClientStateAddressResolver addressResolver)
|
||||||
get
|
|
||||||
{
|
{
|
||||||
var i = 0;
|
this.address = addressResolver;
|
||||||
for (; i < 3; i++)
|
|
||||||
|
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 addr = this.GetBattleBuddyMemberAddress(i);
|
var i = 0;
|
||||||
var member = this.CreateBuddyMemberReference(addr);
|
for (; i < 3; i++)
|
||||||
if (member == null)
|
{
|
||||||
break;
|
var addr = this.GetBattleBuddyMemberAddress(i);
|
||||||
|
var member = this.CreateBuddyMemberReference(addr);
|
||||||
|
if (member == null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the local player's companion is present.
|
/// Gets a value indicating whether the local player's companion is present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CompanionBuddyPresent => this.CompanionBuddy != null;
|
public bool CompanionBuddyPresent => this.CompanionBuddy != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the local player's pet is present.
|
/// Gets a value indicating whether the local player's pet is present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PetBuddyPresent => this.PetBuddy != null;
|
public bool PetBuddyPresent => this.PetBuddy != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the active companion buddy.
|
/// Gets the active companion buddy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BuddyMember? CompanionBuddy
|
public BuddyMember? CompanionBuddy
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
var addr = this.GetCompanionBuddyMemberAddress();
|
get
|
||||||
return this.CreateBuddyMemberReference(addr);
|
{
|
||||||
|
var addr = this.GetCompanionBuddyMemberAddress();
|
||||||
|
return this.CreateBuddyMemberReference(addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the active pet buddy.
|
/// Gets the active pet buddy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BuddyMember? PetBuddy
|
public BuddyMember? PetBuddy
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
var addr = this.GetPetBuddyMemberAddress();
|
get
|
||||||
return this.CreateBuddyMemberReference(addr);
|
{
|
||||||
|
var addr = this.GetPetBuddyMemberAddress();
|
||||||
|
return this.CreateBuddyMemberReference(addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the buddy list.
|
/// Gets the address of the buddy list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IntPtr BuddyListAddress => this.address.BuddyList;
|
internal IntPtr BuddyListAddress => this.address.BuddyList;
|
||||||
|
|
||||||
private static int BuddyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy>();
|
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;
|
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a battle buddy at the specified spawn index.
|
/// Gets a battle buddy at the specified spawn index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Spawn index.</param>
|
/// <param name="index">Spawn index.</param>
|
||||||
/// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns>
|
/// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns>
|
||||||
public BuddyMember? this[int index]
|
public BuddyMember? this[int index]
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
var address = this.GetBattleBuddyMemberAddress(index);
|
get
|
||||||
return this.CreateBuddyMemberReference(address);
|
{
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Gets the address of the companion buddy.
|
/// This collection represents the buddies present in your squadron or trust party.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The memory address of the companion buddy.</returns>
|
public sealed partial class BuddyList : IReadOnlyCollection<BuddyMember>
|
||||||
public unsafe IntPtr GetCompanionBuddyMemberAddress()
|
|
||||||
{
|
{
|
||||||
return (IntPtr)(&this.BuddyListStruct->Companion);
|
/// <inheritdoc/>
|
||||||
}
|
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the address of the pet buddy.
|
public IEnumerator<BuddyMember> GetEnumerator()
|
||||||
/// </summary>
|
{
|
||||||
/// <returns>The memory address of the pet buddy.</returns>
|
for (var i = 0; i < this.Length; i++)
|
||||||
public unsafe IntPtr GetPetBuddyMemberAddress()
|
{
|
||||||
{
|
yield return this[i];
|
||||||
return (IntPtr)(&this.BuddyListStruct->Pet);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the address of the battle buddy at the specified index of the buddy list.
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
/// </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,69 +4,70 @@ using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Buddy address.</param>
|
public unsafe class BuddyMember
|
||||||
internal BuddyMember(IntPtr address)
|
|
||||||
{
|
{
|
||||||
this.Address = 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,164 +16,165 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ClientState"/> class.
|
/// This class represents the state of the game client at the time of access.
|
||||||
/// Set up client state access.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ClientState()
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed class ClientState : IDisposable
|
||||||
{
|
{
|
||||||
this.address = new ClientStateAddressResolver();
|
private readonly ClientStateAddressResolver address;
|
||||||
this.address.Setup();
|
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||||
|
|
||||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
private bool lastConditionNone = true;
|
||||||
|
|
||||||
this.ClientLanguage = Service<DalamudStartInfo>.Get().Language;
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ClientState"/> class.
|
||||||
Service<ObjectTable>.Set(this.address);
|
/// Set up client state access.
|
||||||
|
/// </summary>
|
||||||
Service<FateTable>.Set(this.address);
|
internal ClientState()
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Log.Debug("Is login");
|
this.address = new ClientStateAddressResolver();
|
||||||
this.lastConditionNone = false;
|
this.address.Setup();
|
||||||
this.IsLoggedIn = true;
|
|
||||||
this.Login?.Invoke(this, null);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!condition.Any() && this.lastConditionNone == false)
|
[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()
|
||||||
{
|
{
|
||||||
Log.Debug("Is logout");
|
Service<Condition>.Get().Enable();
|
||||||
this.lastConditionNone = true;
|
Service<GamepadState>.Get().Enable();
|
||||||
this.IsLoggedIn = false;
|
this.setupTerritoryTypeHook.Enable();
|
||||||
this.Logout?.Invoke(this, null);
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,113 +1,114 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Gets the address of the actor table.
|
/// Client state memory address resolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr ObjectTable { get; private set; }
|
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
// We don't need those anymore, but maybe someone else will - let's leave them here for good measure
|
// Static offsets
|
||||||
// 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");
|
/// <summary>
|
||||||
|
/// Gets the address of the actor table.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr ObjectTable { 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 the buddy list.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr BuddyList { get; private set; }
|
||||||
|
|
||||||
this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
|
/// <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.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
|
/// <summary>
|
||||||
|
/// Gets the address of the Group Manager.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr GroupManager { get; private set; }
|
||||||
|
|
||||||
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
/// <summary>
|
||||||
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8;
|
/// Gets the address of the local content id.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr LocalContentId { 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 job gauge data.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr JobGaugeData { get; private set; }
|
||||||
|
|
||||||
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
|
/// <summary>
|
||||||
// lea rcx, ds:1DB9F74h[rax*4] KeyboardState
|
/// Gets the address of the keyboard state.
|
||||||
// movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray
|
/// </summary>
|
||||||
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
public IntPtr KeyboardState { get; private set; }
|
||||||
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");
|
/// <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.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
|
/// <summary>
|
||||||
|
/// Gets the address of the target manager.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr TargetManager { get; private set; }
|
||||||
|
|
||||||
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
|
/// <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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,154 +4,155 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int MaxConditionEntries = 100;
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
private readonly bool[] cache = new bool[MaxConditionEntries];
|
public sealed partial class Condition
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Condition"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
|
|
||||||
internal Condition(ClientStateAddressResolver resolver)
|
|
||||||
{
|
{
|
||||||
this.Address = resolver.ConditionFlags;
|
/// <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;
|
||||||
|
|
||||||
/// <summary>
|
private readonly bool[] cache = new bool[MaxConditionEntries];
|
||||||
/// 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>
|
/// <summary>
|
||||||
/// Event that gets fired when a condition is set.
|
/// Initializes a new instance of the <see cref="Condition"/> class.
|
||||||
/// Should only get fired for actual changes, so the previous value will always be !value.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
|
||||||
public event ConditionChangeDelegate? ConditionChange;
|
internal Condition(ClientStateAddressResolver resolver)
|
||||||
|
|
||||||
/// <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
|
|
||||||
{
|
{
|
||||||
if (flag < 0 || flag >= MaxConditionEntries)
|
this.Address = resolver.ConditionFlags;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
/// <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>
|
/// <summary>
|
||||||
/// Enables the hooks of the Condition class function.
|
/// Event that gets fired when a condition is set.
|
||||||
/// </summary>
|
/// Should only get fired for actual changes, so the previous value will always be !value.
|
||||||
public void Enable()
|
/// </summary>
|
||||||
{
|
public event ConditionChangeDelegate? ConditionChange;
|
||||||
// Initialization
|
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
|
||||||
this.cache[i] = this[i];
|
|
||||||
|
|
||||||
Service<Framework>.Get().Update += this.FrameworkUpdate;
|
/// <summary>
|
||||||
}
|
/// Gets the condition array base pointer.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Address { get; private set; }
|
||||||
|
|
||||||
private void FrameworkUpdate(Framework framework)
|
/// <summary>
|
||||||
{
|
/// Check the value of a specific condition/state flag.
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
/// </summary>
|
||||||
|
/// <param name="flag">The condition flag to check.</param>
|
||||||
|
public unsafe bool this[int flag]
|
||||||
{
|
{
|
||||||
var value = this[i];
|
get
|
||||||
|
|
||||||
if (value != this.cache[i])
|
|
||||||
{
|
{
|
||||||
this.cache[i] = value;
|
if (flag < 0 || flag >= MaxConditionEntries)
|
||||||
|
return false;
|
||||||
|
|
||||||
try
|
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])
|
||||||
{
|
{
|
||||||
this.ConditionChange?.Invoke((ConditionFlag)i, value);
|
this.cache[i] = value;
|
||||||
}
|
|
||||||
catch (Exception ex)
|
try
|
||||||
{
|
{
|
||||||
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>
|
/// <summary>
|
||||||
/// Finalizes an instance of the <see cref="Condition" /> class.
|
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
~Condition()
|
public sealed partial class Condition : IDisposable
|
||||||
{
|
{
|
||||||
this.Dispose(false);
|
private bool isDisposed;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes this instance, alongside its hooks.
|
/// Finalizes an instance of the <see cref="Condition" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
~Condition()
|
||||||
{
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
this.Dispose(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (this.isDisposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
{
|
||||||
Service<Framework>.Get().Update -= this.FrameworkUpdate;
|
this.Dispose(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isDisposed = true;
|
/// <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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,452 +1,453 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// Unused.
|
/// Possible state flags (or conditions as they're called internally) that can be set on the local client.
|
||||||
/// </summary>
|
///
|
||||||
None = 0,
|
/// 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>
|
/// </summary>
|
||||||
/// Unable to execute command under normal conditions.
|
public enum ConditionFlag
|
||||||
/// </summary>
|
{
|
||||||
NormalConditions = 1,
|
/// <summary>
|
||||||
|
/// Unused.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while unconscious.
|
None = 0,
|
||||||
/// </summary>
|
|
||||||
Unconscious = 2,
|
/// <summary>
|
||||||
|
/// Unable to execute command under normal conditions.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command during an emote.
|
NormalConditions = 1,
|
||||||
/// </summary>
|
|
||||||
Emoting = 3,
|
/// <summary>
|
||||||
|
/// Unable to execute command while unconscious.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while mounted.
|
Unconscious = 2,
|
||||||
/// </summary>
|
|
||||||
Mounted = 4,
|
/// <summary>
|
||||||
|
/// Unable to execute command during an emote.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while crafting.
|
Emoting = 3,
|
||||||
/// </summary>
|
|
||||||
Crafting = 5,
|
/// <summary>
|
||||||
|
/// Unable to execute command while mounted.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while gathering.
|
Mounted = 4,
|
||||||
/// </summary>
|
|
||||||
Gathering = 6,
|
/// <summary>
|
||||||
|
/// Unable to execute command while crafting.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while melding materia.
|
Crafting = 5,
|
||||||
/// </summary>
|
|
||||||
MeldingMateria = 7,
|
/// <summary>
|
||||||
|
/// Unable to execute command while gathering.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while operating a siege machine.
|
Gathering = 6,
|
||||||
/// </summary>
|
|
||||||
OperatingSiegeMachine = 8,
|
/// <summary>
|
||||||
|
/// Unable to execute command while melding materia.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while carrying an object.
|
MeldingMateria = 7,
|
||||||
/// </summary>
|
|
||||||
CarryingObject = 9,
|
/// <summary>
|
||||||
|
/// Unable to execute command while operating a siege machine.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while mounted.
|
OperatingSiegeMachine = 8,
|
||||||
/// </summary>
|
|
||||||
Mounted2 = 10,
|
/// <summary>
|
||||||
|
/// Unable to execute command while carrying an object.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while in that position.
|
CarryingObject = 9,
|
||||||
/// </summary>
|
|
||||||
InThatPosition = 11,
|
/// <summary>
|
||||||
|
/// Unable to execute command while mounted.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while chocobo racing.
|
Mounted2 = 10,
|
||||||
/// </summary>
|
|
||||||
ChocoboRacing = 12,
|
/// <summary>
|
||||||
|
/// Unable to execute command while in that position.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while playing a mini-game.
|
InThatPosition = 11,
|
||||||
/// </summary>
|
|
||||||
PlayingMiniGame = 13,
|
/// <summary>
|
||||||
|
/// Unable to execute command while chocobo racing.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while playing Lord of Verminion.
|
ChocoboRacing = 12,
|
||||||
/// </summary>
|
|
||||||
PlayingLordOfVerminion = 14,
|
/// <summary>
|
||||||
|
/// Unable to execute command while playing a mini-game.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while participating in a custom match.
|
PlayingMiniGame = 13,
|
||||||
/// </summary>
|
|
||||||
ParticipatingInCustomMatch = 15,
|
/// <summary>
|
||||||
|
/// Unable to execute command while playing Lord of Verminion.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while performing.
|
PlayingLordOfVerminion = 14,
|
||||||
/// </summary>
|
|
||||||
Performing = 16,
|
/// <summary>
|
||||||
|
/// Unable to execute command while participating in a custom match.
|
||||||
// Unknown17 = 17,
|
/// </summary>
|
||||||
// Unknown18 = 18,
|
ParticipatingInCustomMatch = 15,
|
||||||
// Unknown19 = 19,
|
|
||||||
// Unknown20 = 20,
|
/// <summary>
|
||||||
// Unknown21 = 21,
|
/// Unable to execute command while performing.
|
||||||
// Unknown22 = 22,
|
/// </summary>
|
||||||
// Unknown23 = 23,
|
Performing = 16,
|
||||||
// Unknown24 = 24,
|
|
||||||
|
// Unknown17 = 17,
|
||||||
/// <summary>
|
// Unknown18 = 18,
|
||||||
/// Unable to execute command while occupied.
|
// Unknown19 = 19,
|
||||||
/// </summary>
|
// Unknown20 = 20,
|
||||||
Occupied = 25,
|
// Unknown21 = 21,
|
||||||
|
// Unknown22 = 22,
|
||||||
/// <summary>
|
// Unknown23 = 23,
|
||||||
/// Unable to execute command during combat.
|
// Unknown24 = 24,
|
||||||
/// </summary>
|
|
||||||
InCombat = 26,
|
/// <summary>
|
||||||
|
/// Unable to execute command while occupied.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while casting.
|
Occupied = 25,
|
||||||
/// </summary>
|
|
||||||
Casting = 27,
|
/// <summary>
|
||||||
|
/// Unable to execute command during combat.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
InCombat = 26,
|
||||||
/// </summary>
|
|
||||||
SufferingStatusAffliction = 28,
|
/// <summary>
|
||||||
|
/// Unable to execute command while casting.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
Casting = 27,
|
||||||
/// </summary>
|
|
||||||
SufferingStatusAffliction2 = 29,
|
/// <summary>
|
||||||
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while occupied.
|
SufferingStatusAffliction = 28,
|
||||||
/// </summary>
|
|
||||||
Occupied30 = 30,
|
/// <summary>
|
||||||
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while occupied.
|
SufferingStatusAffliction2 = 29,
|
||||||
/// </summary>
|
|
||||||
// todo: not sure if this is used for other event states/???
|
/// <summary>
|
||||||
OccupiedInEvent = 31,
|
/// Unable to execute command while occupied.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
Occupied30 = 30,
|
||||||
/// Unable to execute command while occupied.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
OccupiedInQuestEvent = 32,
|
/// Unable to execute command while occupied.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
// todo: not sure if this is used for other event states/???
|
||||||
/// Unable to execute command while occupied.
|
OccupiedInEvent = 31,
|
||||||
/// </summary>
|
|
||||||
Occupied33 = 33,
|
/// <summary>
|
||||||
|
/// Unable to execute command while occupied.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while bound by duty.
|
OccupiedInQuestEvent = 32,
|
||||||
/// </summary>
|
|
||||||
BoundByDuty = 34,
|
/// <summary>
|
||||||
|
/// Unable to execute command while occupied.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while occupied.
|
Occupied33 = 33,
|
||||||
/// </summary>
|
|
||||||
OccupiedInCutSceneEvent = 35,
|
/// <summary>
|
||||||
|
/// Unable to execute command while bound by duty.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while in a dueling area.
|
BoundByDuty = 34,
|
||||||
/// </summary>
|
|
||||||
InDuelingArea = 36,
|
/// <summary>
|
||||||
|
/// Unable to execute command while occupied.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while a trade is open.
|
OccupiedInCutSceneEvent = 35,
|
||||||
/// </summary>
|
|
||||||
TradeOpen = 37,
|
/// <summary>
|
||||||
|
/// Unable to execute command while in a dueling area.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while occupied.
|
InDuelingArea = 36,
|
||||||
/// </summary>
|
|
||||||
Occupied38 = 38,
|
/// <summary>
|
||||||
|
/// Unable to execute command while a trade is open.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while occupied.
|
TradeOpen = 37,
|
||||||
/// </summary>
|
|
||||||
Occupied39 = 39,
|
/// <summary>
|
||||||
|
/// Unable to execute command while occupied.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while crafting.
|
Occupied38 = 38,
|
||||||
/// </summary>
|
|
||||||
Crafting40 = 40,
|
/// <summary>
|
||||||
|
/// Unable to execute command while occupied.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while preparing to craft.
|
Occupied39 = 39,
|
||||||
/// </summary>
|
|
||||||
PreparingToCraft = 41,
|
/// <summary>
|
||||||
|
/// Unable to execute command while crafting.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while gathering.
|
Crafting40 = 40,
|
||||||
/// </summary>
|
|
||||||
Gathering42 = 42,
|
/// <summary>
|
||||||
|
/// Unable to execute command while preparing to craft.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while fishing.
|
PreparingToCraft = 41,
|
||||||
/// </summary>
|
|
||||||
Fishing = 43,
|
/// <summary>
|
||||||
|
/// Unable to execute command while gathering.
|
||||||
// Unknown44 = 44,
|
/// </summary>
|
||||||
|
Gathering42 = 42,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while between areas.
|
/// <summary>
|
||||||
/// </summary>
|
/// Unable to execute command while fishing.
|
||||||
BetweenAreas = 45,
|
/// </summary>
|
||||||
|
Fishing = 43,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while stealthed.
|
// Unknown44 = 44,
|
||||||
/// </summary>
|
|
||||||
Stealthed = 46,
|
/// <summary>
|
||||||
|
/// Unable to execute command while between areas.
|
||||||
// Unknown47 = 47,
|
/// </summary>
|
||||||
|
BetweenAreas = 45,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while jumping.
|
/// <summary>
|
||||||
/// </summary>
|
/// Unable to execute command while stealthed.
|
||||||
Jumping = 48,
|
/// </summary>
|
||||||
|
Stealthed = 46,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while auto-run is active.
|
// Unknown47 = 47,
|
||||||
/// </summary>
|
|
||||||
AutorunActive = 49,
|
/// <summary>
|
||||||
|
/// Unable to execute command while jumping.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while occupied.
|
Jumping = 48,
|
||||||
/// </summary>
|
|
||||||
// todo: used for other shits?
|
/// <summary>
|
||||||
OccupiedSummoningBell = 50,
|
/// Unable to execute command while auto-run is active.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
AutorunActive = 49,
|
||||||
/// Unable to execute command while between areas.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
BetweenAreas51 = 51,
|
/// Unable to execute command while occupied.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
// todo: used for other shits?
|
||||||
/// Unable to execute command due to system error.
|
OccupiedSummoningBell = 50,
|
||||||
/// </summary>
|
|
||||||
SystemError = 52,
|
/// <summary>
|
||||||
|
/// Unable to execute command while between areas.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while logging out.
|
BetweenAreas51 = 51,
|
||||||
/// </summary>
|
|
||||||
LoggingOut = 53,
|
/// <summary>
|
||||||
|
/// Unable to execute command due to system error.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command at this location.
|
SystemError = 52,
|
||||||
/// </summary>
|
|
||||||
ConditionLocation = 54,
|
/// <summary>
|
||||||
|
/// Unable to execute command while logging out.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while waiting for duty.
|
LoggingOut = 53,
|
||||||
/// </summary>
|
|
||||||
WaitingForDuty = 55,
|
/// <summary>
|
||||||
|
/// Unable to execute command at this location.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while bound by duty.
|
ConditionLocation = 54,
|
||||||
/// </summary>
|
|
||||||
BoundByDuty56 = 56,
|
/// <summary>
|
||||||
|
/// Unable to execute command while waiting for duty.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command at this time.
|
WaitingForDuty = 55,
|
||||||
/// </summary>
|
|
||||||
Unknown57 = 57,
|
/// <summary>
|
||||||
|
/// Unable to execute command while bound by duty.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while watching a cutscene.
|
BoundByDuty56 = 56,
|
||||||
/// </summary>
|
|
||||||
WatchingCutscene = 58,
|
/// <summary>
|
||||||
|
/// Unable to execute command at this time.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while waiting for Duty Finder.
|
Unknown57 = 57,
|
||||||
/// </summary>
|
|
||||||
WaitingForDutyFinder = 59,
|
/// <summary>
|
||||||
|
/// Unable to execute command while watching a cutscene.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while creating a character.
|
WatchingCutscene = 58,
|
||||||
/// </summary>
|
|
||||||
CreatingCharacter = 60,
|
/// <summary>
|
||||||
|
/// Unable to execute command while waiting for Duty Finder.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while jumping.
|
WaitingForDutyFinder = 59,
|
||||||
/// </summary>
|
|
||||||
Jumping61 = 61,
|
/// <summary>
|
||||||
|
/// Unable to execute command while creating a character.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while the PvP display is active.
|
CreatingCharacter = 60,
|
||||||
/// </summary>
|
|
||||||
PvPDisplayActive = 62,
|
/// <summary>
|
||||||
|
/// Unable to execute command while jumping.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
Jumping61 = 61,
|
||||||
/// </summary>
|
|
||||||
SufferingStatusAffliction63 = 63,
|
/// <summary>
|
||||||
|
/// Unable to execute command while the PvP display is active.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while mounting.
|
PvPDisplayActive = 62,
|
||||||
/// </summary>
|
|
||||||
Mounting = 64,
|
/// <summary>
|
||||||
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while carrying an item.
|
SufferingStatusAffliction63 = 63,
|
||||||
/// </summary>
|
|
||||||
CarryingItem = 65,
|
/// <summary>
|
||||||
|
/// Unable to execute command while mounting.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while using the Party Finder.
|
Mounting = 64,
|
||||||
/// </summary>
|
|
||||||
UsingPartyFinder = 66,
|
/// <summary>
|
||||||
|
/// Unable to execute command while carrying an item.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while using housing functions.
|
CarryingItem = 65,
|
||||||
/// </summary>
|
|
||||||
UsingHousingFunctions = 67,
|
/// <summary>
|
||||||
|
/// Unable to execute command while using the Party Finder.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while transformed.
|
UsingPartyFinder = 66,
|
||||||
/// </summary>
|
|
||||||
Transformed = 68,
|
/// <summary>
|
||||||
|
/// Unable to execute command while using housing functions.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while on the free trial.
|
UsingHousingFunctions = 67,
|
||||||
/// </summary>
|
|
||||||
OnFreeTrial = 69,
|
/// <summary>
|
||||||
|
/// Unable to execute command while transformed.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while being moved.
|
Transformed = 68,
|
||||||
/// </summary>
|
|
||||||
BeingMoved = 70,
|
/// <summary>
|
||||||
|
/// Unable to execute command while on the free trial.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while mounting.
|
OnFreeTrial = 69,
|
||||||
/// </summary>
|
|
||||||
Mounting71 = 71,
|
/// <summary>
|
||||||
|
/// Unable to execute command while being moved.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
BeingMoved = 70,
|
||||||
/// </summary>
|
|
||||||
SufferingStatusAffliction72 = 72,
|
/// <summary>
|
||||||
|
/// Unable to execute command while mounting.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
Mounting71 = 71,
|
||||||
/// </summary>
|
|
||||||
SufferingStatusAffliction73 = 73,
|
/// <summary>
|
||||||
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while registering for a race or match.
|
SufferingStatusAffliction72 = 72,
|
||||||
/// </summary>
|
|
||||||
RegisteringForRaceOrMatch = 74,
|
/// <summary>
|
||||||
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while waiting for a race or match.
|
SufferingStatusAffliction73 = 73,
|
||||||
/// </summary>
|
|
||||||
WaitingForRaceOrMatch = 75,
|
/// <summary>
|
||||||
|
/// Unable to execute command while registering for a race or match.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while waiting for a Triple Triad match.
|
RegisteringForRaceOrMatch = 74,
|
||||||
/// </summary>
|
|
||||||
WaitingForTripleTriadMatch = 76,
|
/// <summary>
|
||||||
|
/// Unable to execute command while waiting for a race or match.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while in flight.
|
WaitingForRaceOrMatch = 75,
|
||||||
/// </summary>
|
|
||||||
InFlight = 77,
|
/// <summary>
|
||||||
|
/// Unable to execute command while waiting for a Triple Triad match.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while watching a cutscene.
|
WaitingForTripleTriadMatch = 76,
|
||||||
/// </summary>
|
|
||||||
WatchingCutscene78 = 78,
|
/// <summary>
|
||||||
|
/// Unable to execute command while in flight.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while delving into a deep dungeon.
|
InFlight = 77,
|
||||||
/// </summary>
|
|
||||||
InDeepDungeon = 79,
|
/// <summary>
|
||||||
|
/// Unable to execute command while watching a cutscene.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while swimming.
|
WatchingCutscene78 = 78,
|
||||||
/// </summary>
|
|
||||||
Swimming = 80,
|
/// <summary>
|
||||||
|
/// Unable to execute command while delving into a deep dungeon.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while diving.
|
InDeepDungeon = 79,
|
||||||
/// </summary>
|
|
||||||
Diving = 81,
|
/// <summary>
|
||||||
|
/// Unable to execute command while swimming.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while registering for a Triple Triad match.
|
Swimming = 80,
|
||||||
/// </summary>
|
|
||||||
RegisteringForTripleTriadMatch = 82,
|
/// <summary>
|
||||||
|
/// Unable to execute command while diving.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while waiting for a Triple Triad match.
|
Diving = 81,
|
||||||
/// </summary>
|
|
||||||
WaitingForTripleTriadMatch83 = 83,
|
/// <summary>
|
||||||
|
/// Unable to execute command while registering for a Triple Triad match.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while participating in a cross-world party or alliance.
|
RegisteringForTripleTriadMatch = 82,
|
||||||
/// </summary>
|
|
||||||
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
/// <summary>
|
||||||
|
/// Unable to execute command while waiting for a Triple Triad match.
|
||||||
// Unknown85 = 85,
|
/// </summary>
|
||||||
|
WaitingForTripleTriadMatch83 = 83,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while playing duty record.
|
/// <summary>
|
||||||
/// </summary>
|
/// Unable to execute command while participating in a cross-world party or alliance.
|
||||||
DutyRecorderPlayback = 86,
|
/// </summary>
|
||||||
|
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while casting.
|
// Unknown85 = 85,
|
||||||
/// </summary>
|
|
||||||
Casting87 = 87,
|
/// <summary>
|
||||||
|
/// Unable to execute command while playing duty record.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command in this state.
|
DutyRecorderPlayback = 86,
|
||||||
/// </summary>
|
|
||||||
InThisState88 = 88,
|
/// <summary>
|
||||||
|
/// Unable to execute command while casting.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command in this state.
|
Casting87 = 87,
|
||||||
/// </summary>
|
|
||||||
InThisState89 = 89,
|
/// <summary>
|
||||||
|
/// Unable to execute command in this state.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while role-playing.
|
InThisState88 = 88,
|
||||||
/// </summary>
|
|
||||||
RolePlaying = 90,
|
/// <summary>
|
||||||
|
/// Unable to execute command in this state.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while bound by duty.
|
InThisState89 = 89,
|
||||||
/// </summary>
|
|
||||||
BoundToDuty97 = 91,
|
/// <summary>
|
||||||
|
/// Unable to execute command while role-playing.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while readying to visit another World.
|
RolePlaying = 90,
|
||||||
/// </summary>
|
|
||||||
ReadyingVisitOtherWorld = 92,
|
/// <summary>
|
||||||
|
/// Unable to execute command while bound by duty.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while waiting to visit another World.
|
BoundToDuty97 = 91,
|
||||||
/// </summary>
|
|
||||||
WaitingToVisitOtherWorld = 93,
|
/// <summary>
|
||||||
|
/// Unable to execute command while readying to visit another World.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while using a parasol.
|
ReadyingVisitOtherWorld = 92,
|
||||||
/// </summary>
|
|
||||||
UsingParasol = 94,
|
/// <summary>
|
||||||
|
/// Unable to execute command while waiting to visit another World.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Unable to execute command while bound by duty.
|
WaitingToVisitOtherWorld = 93,
|
||||||
/// </summary>
|
|
||||||
BoundByDuty95 = 95,
|
/// <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,130 +6,131 @@ using Dalamud.Game.ClientState.Resolvers;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Fate"/> class.
|
/// This class represents an FFXIV Fate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this fate in memory.</param>
|
public unsafe partial class Fate : IEquatable<Fate>
|
||||||
internal Fate(IntPtr address)
|
|
||||||
{
|
{
|
||||||
this.Address = 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of this Fate in memory.
|
/// This class represents an FFXIV Fate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr Address { get; }
|
public unsafe partial class Fate
|
||||||
|
|
||||||
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)
|
/// <summary>
|
||||||
return Equals(fate1, fate2);
|
/// Gets the Fate ID of this <see cref="Fate" />.
|
||||||
|
/// </summary>
|
||||||
|
public ushort FateId => this.Struct->FateId;
|
||||||
|
|
||||||
return fate1.Equals(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
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,32 +1,33 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// The Fate is active.
|
/// This represents the state of a single Fate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Running = 0x02,
|
public enum FateState : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Fate is active.
|
||||||
|
/// </summary>
|
||||||
|
Running = 0x02,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Fate has ended.
|
/// The Fate has ended.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Ended = 0x04,
|
Ended = 0x04,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The player failed the Fate.
|
/// The player failed the Fate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Failed = 0x05,
|
Failed = 0x05,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Fate is preparing to run.
|
/// The Fate is preparing to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Preparation = 0x07,
|
Preparation = 0x07,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Fate is preparing to end.
|
/// The Fate is preparing to end.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingForEnd = 0x08,
|
WaitingForEnd = 0x08,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,142 +6,143 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="FateTable"/> class.
|
/// This collection represents the currently available Fate events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">Client state address resolver.</param>
|
[PluginInterface]
|
||||||
internal FateTable(ClientStateAddressResolver addressResolver)
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed partial class FateTable
|
||||||
{
|
{
|
||||||
this.address = addressResolver;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
|
/// <summary>
|
||||||
}
|
/// Initializes a new instance of the <see cref="FateTable"/> class.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="addressResolver">Client state address resolver.</param>
|
||||||
/// Gets the address of the Fate table.
|
internal FateTable(ClientStateAddressResolver addressResolver)
|
||||||
/// </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;
|
this.address = addressResolver;
|
||||||
if (fateTable == IntPtr.Zero)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Sonar used this to check if the table was safe to read
|
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets the address of the Fate table.
|
/// Gets the address of the Fate table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal unsafe IntPtr FateTableAddress
|
public IntPtr Address => this.address.FateTablePtr;
|
||||||
{
|
|
||||||
get
|
/// <summary>
|
||||||
|
/// Gets the amount of currently active Fates.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe int Length
|
||||||
{
|
{
|
||||||
if (this.address.FateTablePtr == IntPtr.Zero)
|
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;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return *(IntPtr*)this.address.FateTablePtr;
|
var fateTable = this.FateTableAddress;
|
||||||
|
if (fateTable == IntPtr.Zero)
|
||||||
|
return IntPtr.Zero;
|
||||||
|
|
||||||
|
var firstFate = this.Struct->FirstFatePtr;
|
||||||
|
return *(IntPtr*)(firstFate + (8 * index));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
|
/// <summary>
|
||||||
|
/// Create a reference to a FFXIV actor.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Get an actor at the specified spawn index.
|
/// <param name="offset">The offset of the actor in memory.</param>
|
||||||
/// </summary>
|
/// <returns><see cref="Fate"/> object containing requested data.</returns>
|
||||||
/// <param name="index">Spawn index.</param>
|
public Fate? CreateFateReference(IntPtr offset)
|
||||||
/// <returns>A <see cref="Fate"/> at the specified spawn index.</returns>
|
|
||||||
public Fate? this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
var address = this.GetFateAddress(index);
|
var clientState = Service<ClientState>.Get();
|
||||||
return this.CreateFateReference(address);
|
|
||||||
|
if (clientState.LocalContentId == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (offset == IntPtr.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Fate(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the Fate at the specified index of the fate table.
|
/// This collection represents the currently available Fate events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">The index of the Fate.</param>
|
public sealed partial class FateTable : IReadOnlyCollection<Fate>
|
||||||
/// <returns>The memory address of the Fate.</returns>
|
|
||||||
public unsafe IntPtr GetFateAddress(int index)
|
|
||||||
{
|
{
|
||||||
if (index >= this.Length)
|
/// <inheritdoc/>
|
||||||
return IntPtr.Zero;
|
int IReadOnlyCollection<Fate>.Count => this.Length;
|
||||||
|
|
||||||
var fateTable = this.FateTableAddress;
|
/// <inheritdoc/>
|
||||||
if (fateTable == IntPtr.Zero)
|
public IEnumerator<Fate> GetEnumerator()
|
||||||
return IntPtr.Zero;
|
{
|
||||||
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
yield return this[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var firstFate = this.Struct->FirstFatePtr;
|
/// <inheritdoc/>
|
||||||
return *(IntPtr*)(firstFate + (8 * index));
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
}
|
|
||||||
|
|
||||||
/// <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,95 +1,96 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// No buttons pressed.
|
/// Bitmask of the Button ushort used by the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
[Flags]
|
||||||
|
public enum GamepadButtons : ushort
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No buttons pressed.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad up.
|
/// Digipad up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadUp = 0x0001,
|
DpadUp = 0x0001,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad down.
|
/// Digipad down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadDown = 0x0002,
|
DpadDown = 0x0002,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad left.
|
/// Digipad left.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadLeft = 0x0004,
|
DpadLeft = 0x0004,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad right.
|
/// Digipad right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadRight = 0x0008,
|
DpadRight = 0x0008,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// North action button. Triangle on PS, Y on Xbox.
|
/// North action button. Triangle on PS, Y on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
North = 0x0010,
|
North = 0x0010,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// South action button. Cross on PS, A on Xbox.
|
/// South action button. Cross on PS, A on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
South = 0x0020,
|
South = 0x0020,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// West action button. Square on PS, X on Xbos.
|
/// West action button. Square on PS, X on Xbos.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
West = 0x0040,
|
West = 0x0040,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// East action button. Circle on PS, B on Xbox.
|
/// East action button. Circle on PS, B on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
East = 0x0080,
|
East = 0x0080,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First button on left shoulder side.
|
/// First button on left shoulder side.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
L1 = 0x0100,
|
L1 = 0x0100,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Second button on left shoulder side. Analog input lost in this bitmask.
|
/// Second button on left shoulder side. Analog input lost in this bitmask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
L2 = 0x0200,
|
L2 = 0x0200,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Press on left analogue stick.
|
/// Press on left analogue stick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
L3 = 0x0400,
|
L3 = 0x0400,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First button on right shoulder.
|
/// First button on right shoulder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
R1 = 0x0800,
|
R1 = 0x0800,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Second button on right shoulder. Analog input lost in this bitmask.
|
/// Second button on right shoulder. Analog input lost in this bitmask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
R2 = 0x1000,
|
R2 = 0x1000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Press on right analogue stick.
|
/// Press on right analogue stick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
R3 = 0x2000,
|
R3 = 0x2000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button on the right inner side of the controller. Options on PS, Start on Xbox.
|
/// Button on the right inner side of the controller. Options on PS, Start on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Start = 0x8000,
|
Start = 0x8000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
|
/// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Select = 0x4000,
|
Select = 0x4000,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,76 @@
|
||||||
using System.Runtime.InteropServices;
|
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>
|
/// <summary>
|
||||||
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
/// 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>
|
/// </summary>
|
||||||
[FieldOffset(0x88)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public int LeftStickX;
|
public struct GamepadInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(0x88)]
|
||||||
|
public int LeftStickX;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(0x8C)]
|
[FieldOffset(0x8C)]
|
||||||
public int LeftStickY;
|
public int LeftStickY;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(0x90)]
|
[FieldOffset(0x90)]
|
||||||
public int RightStickX;
|
public int RightStickX;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(0x94)]
|
[FieldOffset(0x94)]
|
||||||
public int RightStickY;
|
public int RightStickY;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0x98)]
|
[FieldOffset(0x98)]
|
||||||
public ushort ButtonsRaw;
|
public ushort ButtonsRaw;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0x9C)]
|
[FieldOffset(0x9C)]
|
||||||
public ushort ButtonsPressed;
|
public ushort ButtonsPressed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0xA0)]
|
[FieldOffset(0xA0)]
|
||||||
public ushort ButtonsReleased;
|
public ushort ButtonsReleased;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0xA4)]
|
[FieldOffset(0xA4)]
|
||||||
public ushort ButtonsRepeat;
|
public ushort ButtonsRepeat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,257 +4,258 @@ using Dalamud.Hooking;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GamepadState" /> class.
|
/// Exposes the game gamepad state to dalamud.
|
||||||
|
///
|
||||||
|
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
|
public unsafe class GamepadState : IDisposable
|
||||||
public GamepadState(ClientStateAddressResolver resolver)
|
|
||||||
{
|
{
|
||||||
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
private readonly Hook<ControllerPoll> gamepadPoll;
|
||||||
this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
private bool isDisposed;
|
||||||
/// Finalizes an instance of the <see cref="GamepadState" /> class.
|
|
||||||
/// </summary>
|
|
||||||
~GamepadState()
|
|
||||||
{
|
|
||||||
this.Dispose(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
private int leftStickX;
|
||||||
|
private int leftStickY;
|
||||||
|
private int rightStickX;
|
||||||
|
private int rightStickY;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the pointer to the current instance of the GamepadInput struct.
|
/// Initializes a new instance of the <see cref="GamepadState" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr GamepadInputAddress { get; private set; }
|
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
|
||||||
|
public GamepadState(ClientStateAddressResolver resolver)
|
||||||
/// <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
|
|
||||||
{
|
{
|
||||||
this.GamepadInputAddress = gamepadInput;
|
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
||||||
var input = (GamepadInput*)gamepadInput;
|
this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
|
||||||
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;
|
|
||||||
|
|
||||||
if (this.NavEnableGamepad)
|
/// <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
|
||||||
{
|
{
|
||||||
input->LeftStickX = 0;
|
this.GamepadInputAddress = gamepadInput;
|
||||||
input->LeftStickY = 0;
|
var input = (GamepadInput*)gamepadInput;
|
||||||
input->RightStickX = 0;
|
this.leftStickX = input->LeftStickX;
|
||||||
input->RightStickY = 0;
|
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;
|
||||||
|
|
||||||
// NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
|
if (this.NavEnableGamepad)
|
||||||
// and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
|
{
|
||||||
// It does block, however, all input to the game.
|
input->LeftStickX = 0;
|
||||||
// Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
|
input->LeftStickY = 0;
|
||||||
// and the digipad (in some situations, but thankfully not in menus) functional.
|
input->RightStickX = 0;
|
||||||
// We can either:
|
input->RightStickY = 0;
|
||||||
// (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
|
// NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
|
||||||
// (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
|
// and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
|
||||||
// Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
|
// It does block, however, all input to the game.
|
||||||
// because of the other blocked input)
|
// Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
|
||||||
// `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on
|
// and the digipad (in some situations, but thankfully not in menus) functional.
|
||||||
// `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
|
// We can either:
|
||||||
// This is debatable.
|
// (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
|
||||||
// ImGui itself does not care either way as it uses the Raw values and does its own state handling.
|
// (b) ignore it as so far it seems only a 'visual' error
|
||||||
const ushort deletionMask = (ushort)(~GamepadButtons.L2
|
// (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
|
||||||
& ~GamepadButtons.R2
|
// Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
|
||||||
& ~GamepadButtons.DpadDown
|
// because of the other blocked input)
|
||||||
& ~GamepadButtons.DpadLeft
|
// `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on
|
||||||
& ~GamepadButtons.DpadUp
|
// `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
|
||||||
& ~GamepadButtons.DpadRight);
|
// This is debatable.
|
||||||
input->ButtonsRaw &= deletionMask;
|
// ImGui itself does not care either way as it uses the Raw values and does its own state handling.
|
||||||
input->ButtonsPressed = 0;
|
const ushort deletionMask = (ushort)(~GamepadButtons.L2
|
||||||
input->ButtonsReleased = 0;
|
& ~GamepadButtons.R2
|
||||||
input->ButtonsRepeat = 0;
|
& ~GamepadButtons.DpadDown
|
||||||
return 0;
|
& ~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) Not so sure about the return value, does not seem to matter if we return the
|
this.isDisposed = true;
|
||||||
// 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,22 +1,23 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// Inactive type.
|
/// DRG Blood of the Dragon state types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NONE = 0,
|
public enum BOTDState : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Inactive type.
|
||||||
|
/// </summary>
|
||||||
|
NONE = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Blood of the Dragon is active.
|
/// Blood of the Dragon is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BOTD = 1,
|
BOTD = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Life of the Dragon is active.
|
/// Life of the Dragon is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LOTD = 2,
|
LOTD = 2,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,53 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AST Arcanum (card) types.
|
|
||||||
/// </summary>
|
|
||||||
public enum CardType : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No card.
|
/// AST Arcanum (card) types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NONE = 0,
|
public enum CardType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No card.
|
||||||
|
/// </summary>
|
||||||
|
NONE = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Balance card.
|
/// The Balance card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BALANCE = 1,
|
BALANCE = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Bole card.
|
/// The Bole card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BOLE = 2,
|
BOLE = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Arrow card.
|
/// The Arrow card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ARROW = 3,
|
ARROW = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Spear card.
|
/// The Spear card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SPEAR = 4,
|
SPEAR = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Ewer card.
|
/// The Ewer card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EWER = 5,
|
EWER = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Spire card.
|
/// The Spire card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SPIRE = 6,
|
SPIRE = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Lord of Crowns card.
|
/// The Lord of Crowns card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LORD = 0x70,
|
LORD = 0x70,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Lady of Crowns card.
|
/// The Lady of Crowns card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LADY = 0x80,
|
LADY = 0x80,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SCH Dismissed fairy types.
|
|
||||||
/// </summary>
|
|
||||||
public enum DismissedFairy : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dismissed fairy is Eos.
|
/// SCH Dismissed fairy types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EOS = 6,
|
public enum DismissedFairy : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Dismissed fairy is Eos.
|
||||||
|
/// </summary>
|
||||||
|
EOS = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dismissed fairy is Selene.
|
/// Dismissed fairy is Selene.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SELENE = 7,
|
SELENE = 7,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// NIN Mudra types.
|
|
||||||
/// </summary>
|
|
||||||
public enum Mudras : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ten mudra.
|
/// NIN Mudra types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TEN = 1,
|
public enum Mudras : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ten mudra.
|
||||||
|
/// </summary>
|
||||||
|
TEN = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Chi mudra.
|
/// Chi mudra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CHI = 2,
|
CHI = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Jin mudra.
|
/// Jin mudra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
JIN = 3,
|
JIN = 3,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SMN summoned pet glam types.
|
|
||||||
/// </summary>
|
|
||||||
public enum PetGlam : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No pet glam.
|
/// SMN summoned pet glam types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NONE = 0,
|
public enum PetGlam : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No pet glam.
|
||||||
|
/// </summary>
|
||||||
|
NONE = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Emerald carbuncle pet glam.
|
/// Emerald carbuncle pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EMERALD = 1,
|
EMERALD = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Topaz carbuncle pet glam.
|
/// Topaz carbuncle pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TOPAZ = 2,
|
TOPAZ = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ruby carbuncle pet glam.
|
/// Ruby carbuncle pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RUBY = 3,
|
RUBY = 3,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AST Divination seal types.
|
|
||||||
/// </summary>
|
|
||||||
public enum SealType : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No seal.
|
/// AST Divination seal types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NONE = 0,
|
public enum SealType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No seal.
|
||||||
|
/// </summary>
|
||||||
|
NONE = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sun seal.
|
/// Sun seal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SUN = 1,
|
SUN = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moon seal.
|
/// Moon seal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MOON = 2,
|
MOON = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Celestial seal.
|
/// Celestial seal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CELESTIAL = 3,
|
CELESTIAL = 3,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,31 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// No Sen.
|
/// Samurai Sen types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NONE = 0,
|
[Flags]
|
||||||
|
public enum Sen : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No Sen.
|
||||||
|
/// </summary>
|
||||||
|
NONE = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Setsu Sen type.
|
/// Setsu Sen type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SETSU = 1 << 0,
|
SETSU = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Getsu Sen type.
|
/// Getsu Sen type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GETSU = 1 << 1,
|
GETSU = 1 << 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ka Sen type.
|
/// Ka Sen type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
KA = 1 << 2,
|
KA = 1 << 2,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BRD Song types.
|
|
||||||
/// </summary>
|
|
||||||
public enum Song : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No song is active type.
|
/// BRD Song types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NONE = 0,
|
public enum Song : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No song is active type.
|
||||||
|
/// </summary>
|
||||||
|
NONE = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mage's Ballad type.
|
/// Mage's Ballad type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MAGE = 5,
|
MAGE = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Army's Paeon type.
|
/// Army's Paeon type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ARMY = 10,
|
ARMY = 10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Wanderer's Minuet type.
|
/// The Wanderer's Minuet type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WANDERER = 15,
|
WANDERER = 15,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SMN summoned pet types.
|
|
||||||
/// </summary>
|
|
||||||
public enum SummonPet : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No pet.
|
/// SMN summoned pet types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NONE = 0,
|
public enum SummonPet : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No pet.
|
||||||
|
/// </summary>
|
||||||
|
NONE = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The summoned pet Ifrit.
|
/// The summoned pet Ifrit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IFRIT = 3,
|
IFRIT = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The summoned pet Titan.
|
/// The summoned pet Titan.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TITAN = 4,
|
TITAN = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The summoned pet Garuda.
|
/// The summoned pet Garuda.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GARUDA = 5,
|
GARUDA = 5,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,47 +7,48 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="JobGauges"/> class.
|
/// This class converts in-memory Job gauge data to structs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
|
[PluginInterface]
|
||||||
public JobGauges(ClientStateAddressResolver addressResolver)
|
[InterfaceVersion("1.0")]
|
||||||
|
public class JobGauges
|
||||||
{
|
{
|
||||||
this.Address = addressResolver.JobGaugeData;
|
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||||
|
|
||||||
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
|
/// <summary>
|
||||||
}
|
/// Initializes a new instance of the <see cref="JobGauges"/> class.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
|
||||||
/// Gets the address of the JobGauge data.
|
public JobGauges(ClientStateAddressResolver addressResolver)
|
||||||
/// </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);
|
this.Address = addressResolver.JobGaugeData;
|
||||||
|
|
||||||
|
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (T)gauge;
|
/// <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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,38 +2,39 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ASTGauge"/> class.
|
/// In-memory AST job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class ASTGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.AstrologianGauge>
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets the currently drawn <see cref="CardType"/>.
|
/// Gets the currently drawn <see cref="CardType"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Currently drawn <see cref="CardType"/>.</returns>
|
/// <returns>Currently drawn <see cref="CardType"/>.</returns>
|
||||||
public CardType DrawnCard => (CardType)this.Struct->Card;
|
public CardType DrawnCard => (CardType)this.Struct->Card;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if a <see cref="SealType"/> is currently active on the divination gauge.
|
/// Check if a <see cref="SealType"/> is currently active on the divination gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seal">The <see cref="SealType"/> to check for.</param>
|
/// <param name="seal">The <see cref="SealType"/> to check for.</param>
|
||||||
/// <returns>If the given Seal is currently divined.</returns>
|
/// <returns>If the given Seal is currently divined.</returns>
|
||||||
public unsafe bool ContainsSeal(SealType seal)
|
public unsafe bool ContainsSeal(SealType seal)
|
||||||
{
|
{
|
||||||
if (this.Struct->Seals[0] == (byte)seal) return true;
|
if (this.Struct->Seals[0] == (byte)seal) return true;
|
||||||
if (this.Struct->Seals[1] == (byte)seal) return true;
|
if (this.Struct->Seals[1] == (byte)seal) return true;
|
||||||
if (this.Struct->Seals[2] == (byte)seal) return true;
|
if (this.Struct->Seals[2] == (byte)seal) return true;
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,67 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BLMGauge"/> class.
|
/// In-memory BLM job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class BLMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BlackMageGauge>
|
||||||
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,39 +2,40 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BRDGauge"/> class.
|
/// In-memory BRD job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BardGauge>
|
||||||
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,59 +1,60 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DNCGauge"/> class.
|
/// In-memory DNC job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class DNCGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DancerGauge>
|
||||||
internal DNCGauge(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
{
|
||||||
}
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DNCGauge"/> class.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Gets the number of feathers available.
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
/// </summary>
|
internal DNCGauge(IntPtr address)
|
||||||
public byte Feathers => this.Struct->Feathers;
|
: base(address)
|
||||||
|
|
||||||
/// <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 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 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,34 +2,35 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DRGGauge"/> class.
|
/// In-memory DRG job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class DRGGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DragoonGauge>
|
||||||
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,39 +1,40 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DRKGauge"/> class.
|
/// In-memory DRK job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class DRKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DarkKnightGauge>
|
||||||
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,33 +1,34 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GNBGauge"/> class.
|
/// In-memory GNB job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class GNBGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.GunbreakerGauge>
|
||||||
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,23 +1,24 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="JobGaugeBase"/> class.
|
/// Base job gauge class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public abstract unsafe class JobGaugeBase
|
||||||
internal JobGaugeBase(IntPtr address)
|
|
||||||
{
|
{
|
||||||
this.Address = 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>
|
/// <summary>
|
||||||
/// Gets the address of this job gauge in memory.
|
/// Gets the address of this job gauge in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr Address { get; }
|
public IntPtr Address { get; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,25 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="JobGaugeBase{T}"/> class.
|
/// Base job gauge class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
/// <typeparam name="T">The underlying FFXIVClientStructs type.</typeparam>
|
||||||
internal JobGaugeBase(IntPtr address)
|
public unsafe class JobGaugeBase<T> : JobGaugeBase where T : unmanaged
|
||||||
: 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>
|
/// <summary>
|
||||||
/// Gets an unsafe struct pointer of this job gauge.
|
/// Gets an unsafe struct pointer of this job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private protected T* Struct => (T*)this.Address;
|
private protected T* Struct => (T*)this.Address;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,56 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MCHGauge"/> class.
|
/// In-memory MCH job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class MCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MachinistGauge>
|
||||||
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,23 +1,24 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MNKGauge"/> class.
|
/// In-memory MNK job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class MNKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MonkGauge>
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets the number of Chakra available.
|
/// Gets the number of Chakra available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte Chakra => this.Struct->Chakra;
|
public byte Chakra => this.Struct->Chakra;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,34 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="NINGauge"/> class.
|
/// In-memory NIN job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of the gauge.</param>
|
public unsafe class NINGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.NinjaGauge>
|
||||||
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,23 +1,24 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PLDGauge"/> class.
|
/// In-memory PLD job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class PLDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.PaladinGauge>
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets the current level of the Oath gauge.
|
/// Gets the current level of the Oath gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte OathGauge => this.Struct->OathGauge;
|
public byte OathGauge => this.Struct->OathGauge;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,29 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RDMGauge"/> class.
|
/// In-memory RDM job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class RDMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.RedMageGauge>
|
||||||
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,52 +2,53 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SAMGauge"/> class.
|
/// In-memory SAM job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class SAMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SamuraiGauge>
|
||||||
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,39 +2,40 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SCHGauge"/> class.
|
/// In-memory SCH job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class SCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.ScholarGauge>
|
||||||
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,58 +2,59 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SMNGauge"/> class.
|
/// In-memory SMN job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class SMNGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SummonerGauge>
|
||||||
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,23 +1,24 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="WARGauge"/> class.
|
/// In-memory WAR job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class WARGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WarriorGauge>
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets the amount of wrath in the Beast gauge.
|
/// Gets the amount of wrath in the Beast gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte BeastGauge => this.Struct->BeastGauge;
|
public byte BeastGauge => this.Struct->BeastGauge;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,34 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="WHMGauge"/> class.
|
/// In-memory WHM job gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
public unsafe class WHMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WhiteMageGauge>
|
||||||
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,154 +6,155 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="KeyState"/> class.
|
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
/// <remarks>
|
||||||
public KeyState(ClientStateAddressResolver addressResolver)
|
/// 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
|
||||||
{
|
{
|
||||||
var moduleBaseAddress = Service<SigScanner>.Get().Module.BaseAddress;
|
// 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;
|
||||||
|
|
||||||
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
/// <summary>
|
||||||
this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);
|
/// Initializes a new instance of the <see cref="KeyState"/> class.
|
||||||
|
/// </summary>
|
||||||
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
|
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||||
}
|
public KeyState(ClientStateAddressResolver addressResolver)
|
||||||
|
|
||||||
/// <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;
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,27 +1,28 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// Invalid BattleNpc.
|
/// An Enum describing possible BattleNpc kinds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
public enum BattleNpcSubKind : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid BattleNpc.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BattleNpc representing a Pet.
|
/// BattleNpc representing a Pet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Pet = 2,
|
Pet = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BattleNpc representing a Chocobo.
|
/// BattleNpc representing a Chocobo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Chocobo = 3,
|
Chocobo = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BattleNpc representing a standard enemy.
|
/// BattleNpc representing a standard enemy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Enemy = 5,
|
Enemy = 5,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,138 +1,139 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// The race of the character.
|
/// This enum describes the indices of the Customize array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Race = 0x00,
|
// 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,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The gender of the character.
|
/// The gender of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Gender = 0x01,
|
Gender = 0x01,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tribe of the character.
|
/// The tribe of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Tribe = 0x04,
|
Tribe = 0x04,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The height of the character.
|
/// The height of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Height = 0x03,
|
Height = 0x03,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The model type of the character.
|
/// The model type of the character.
|
||||||
/// </summary>
|
/// </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
|
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>
|
/// <summary>
|
||||||
/// The face type of the character.
|
/// The face type of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FaceType = 0x05,
|
FaceType = 0x05,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hair of the character.
|
/// The hair of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HairStyle = 0x06,
|
HairStyle = 0x06,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the character has hair highlights.
|
/// Whether or not the character has hair highlights.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HasHighlights = 0x07, // negative to enable, positive to disable
|
HasHighlights = 0x07, // negative to enable, positive to disable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The skin color of the character.
|
/// The skin color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SkinColor = 0x08,
|
SkinColor = 0x08,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The eye color of the character.
|
/// The eye color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EyeColor = 0x09, // color of character's right eye
|
EyeColor = 0x09, // color of character's right eye
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hair color of the character.
|
/// The hair color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HairColor = 0x0A, // main color
|
HairColor = 0x0A, // main color
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The highlights hair color of the character.
|
/// The highlights hair color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HairColor2 = 0x0B, // highlights color
|
HairColor2 = 0x0B, // highlights color
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The face features of the character.
|
/// The face features of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small
|
FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The color of the face features of the character.
|
/// The color of the face features of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FaceFeaturesColor = 0x0D,
|
FaceFeaturesColor = 0x0D,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The eyebrows of the character.
|
/// The eyebrows of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Eyebrows = 0x0E,
|
Eyebrows = 0x0E,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The 2nd eye color of the character.
|
/// The 2nd eye color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EyeColor2 = 0x0F, // color of character's left eye
|
EyeColor2 = 0x0F, // color of character's left eye
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The eye shape of the character.
|
/// The eye shape of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EyeShape = 0x10,
|
EyeShape = 0x10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The nose shape of the character.
|
/// The nose shape of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NoseShape = 0x11,
|
NoseShape = 0x11,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The jaw shape of the character.
|
/// The jaw shape of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
JawShape = 0x12,
|
JawShape = 0x12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The lip style of the character.
|
/// The lip style of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour)
|
LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The lip color of the character.
|
/// The lip color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LipColor = 0x14,
|
LipColor = 0x14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The race feature size of the character.
|
/// The race feature size of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RaceFeatureSize = 0x15,
|
RaceFeatureSize = 0x15,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The race feature type of the character.
|
/// The race feature type of the character.
|
||||||
/// </summary>
|
/// </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
|
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>
|
/// <summary>
|
||||||
/// The bust size of the character.
|
/// The bust size of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
|
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The face paint of the character.
|
/// The face paint of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Facepaint = 0x18,
|
Facepaint = 0x18,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The face paint color of the character.
|
/// The face paint color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FacepaintColor = 0x19,
|
FacepaintColor = 0x19,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,82 +1,83 @@
|
||||||
namespace Dalamud.Game.ClientState.Objects.Enums;
|
namespace Dalamud.Game.ClientState.Objects.Enums
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enum describing possible entity kinds.
|
|
||||||
/// </summary>
|
|
||||||
public enum ObjectKind : byte
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid character.
|
/// Enum describing possible entity kinds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0x00,
|
public enum ObjectKind : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid character.
|
||||||
|
/// </summary>
|
||||||
|
None = 0x00,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing player characters.
|
/// Objects representing player characters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Player = 0x01,
|
Player = 0x01,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing battle NPCs.
|
/// Objects representing battle NPCs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BattleNpc = 0x02,
|
BattleNpc = 0x02,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing event NPCs.
|
/// Objects representing event NPCs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EventNpc = 0x03,
|
EventNpc = 0x03,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing treasures.
|
/// Objects representing treasures.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Treasure = 0x04,
|
Treasure = 0x04,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing aetherytes.
|
/// Objects representing aetherytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Aetheryte = 0x05,
|
Aetheryte = 0x05,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing gathering points.
|
/// Objects representing gathering points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GatheringPoint = 0x06,
|
GatheringPoint = 0x06,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing event objects.
|
/// Objects representing event objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EventObj = 0x07,
|
EventObj = 0x07,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing mounts.
|
/// Objects representing mounts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MountType = 0x08,
|
MountType = 0x08,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing minions.
|
/// Objects representing minions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Companion = 0x09, // Minion
|
Companion = 0x09, // Minion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing retainers.
|
/// Objects representing retainers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Retainer = 0x0A,
|
Retainer = 0x0A,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing area objects.
|
/// Objects representing area objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Area = 0x0B,
|
Area = 0x0B,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing housing objects.
|
/// Objects representing housing objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Housing = 0x0C,
|
Housing = 0x0C,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing cutscene objects.
|
/// Objects representing cutscene objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Cutscene = 0x0D,
|
Cutscene = 0x0D,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing card stand objects.
|
/// Objects representing card stand objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CardStand = 0x0E,
|
CardStand = 0x0E,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,56 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// No status flags set.
|
/// Enum describing possible status flags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
[Flags]
|
||||||
|
public enum StatusFlags : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No status flags set.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hostile character.
|
/// Hostile character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Hostile = 1,
|
Hostile = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character in combat.
|
/// Character in combat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InCombat = 2,
|
InCombat = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character weapon is out.
|
/// Character weapon is out.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WeaponOut = 4,
|
WeaponOut = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character offhand is out.
|
/// Character offhand is out.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OffhandOut = 8,
|
OffhandOut = 8,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is a party member.
|
/// Character is a party member.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PartyMember = 16,
|
PartyMember = 16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is a alliance member.
|
/// Character is a alliance member.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AllianceMember = 32,
|
AllianceMember = 32,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is in friend list.
|
/// Character is in friend list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Friend = 64,
|
Friend = 64,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is casting.
|
/// Character is casting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IsCasting = 128,
|
IsCasting = 128,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,139 +9,140 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ObjectTable"/> class.
|
/// This collection represents the currently spawned FFXIV game objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">Client state address resolver.</param>
|
[PluginInterface]
|
||||||
internal ObjectTable(ClientStateAddressResolver addressResolver)
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed partial class ObjectTable
|
||||||
{
|
{
|
||||||
this.address = addressResolver;
|
private const int ObjectTableLength = 424;
|
||||||
|
|
||||||
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
|
private readonly ClientStateAddressResolver address;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the object table.
|
/// Initializes a new instance of the <see cref="ObjectTable"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr Address => this.address.ObjectTable;
|
/// <param name="addressResolver">Client state address resolver.</param>
|
||||||
|
internal ObjectTable(ClientStateAddressResolver addressResolver)
|
||||||
/// <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);
|
this.address = addressResolver;
|
||||||
return this.CreateObjectReference(address);
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Search for a game object by their Object ID.
|
/// This collection represents the currently spawned FFXIV game objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="objectId">Object ID to find.</param>
|
public sealed partial class ObjectTable : IReadOnlyCollection<GameObject>
|
||||||
/// <returns>A game object or null.</returns>
|
|
||||||
public GameObject? SearchById(uint objectId)
|
|
||||||
{
|
{
|
||||||
if (objectId is GameObject.InvalidGameObjectId or 0)
|
/// <inheritdoc/>
|
||||||
return null;
|
int IReadOnlyCollection<GameObject>.Count => this.Length;
|
||||||
|
|
||||||
foreach (var obj in this)
|
/// <inheritdoc/>
|
||||||
|
public IEnumerator<GameObject> GetEnumerator()
|
||||||
{
|
{
|
||||||
if (obj == null)
|
for (var i = 0; i < ObjectTableLength; i++)
|
||||||
continue;
|
{
|
||||||
|
var obj = this[i];
|
||||||
|
|
||||||
if (obj.ObjectId == objectId)
|
if (obj == null)
|
||||||
return obj;
|
continue;
|
||||||
|
|
||||||
|
yield return obj;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
/// <inheritdoc/>
|
||||||
}
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
/// <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,28 +2,29 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BattleNpc"/> class.
|
/// This class represents a battle NPC.
|
||||||
/// Set up a new BattleNpc with the provided memory representation.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this actor in memory.</param>
|
public unsafe class BattleNpc : BattleChara
|
||||||
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,20 +2,21 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="EventObj"/> class.
|
/// This class represents an EventObj.
|
||||||
/// Set up a new EventObj with the provided memory representation.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this event object in memory.</param>
|
public unsafe class EventObj : GameObject
|
||||||
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,20 +2,21 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Npc"/> class.
|
/// This class represents a NPC.
|
||||||
/// Set up a new NPC with the provided memory representation.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this actor in memory.</param>
|
public unsafe class Npc : Character
|
||||||
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,35 +3,36 @@ using System;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
|
/// This class represents a player character.
|
||||||
/// This represents a player character.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this actor in memory.</param>
|
public unsafe class PlayerCharacter : BattleChara
|
||||||
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,160 +4,161 @@ using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TargetManager"/> class.
|
/// Get and set various kinds of targets for the player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
[PluginInterface]
|
||||||
internal TargetManager(ClientStateAddressResolver addressResolver)
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed unsafe class TargetManager
|
||||||
{
|
{
|
||||||
this.address = 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,65 +2,66 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Statuses;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BattleChara"/> class.
|
/// This class represents the battle characters.
|
||||||
/// This represents a battle character.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this character in memory.</param>
|
public unsafe class BattleChara : Character
|
||||||
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,101 +5,102 @@ using Dalamud.Game.ClientState.Resolvers;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Character"/> class.
|
/// This class represents the base for non-static entities.
|
||||||
/// This represents a non-static entity.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this character in memory.</param>
|
public unsafe class Character : GameObject
|
||||||
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,170 +5,171 @@ using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
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>
|
/// <summary>
|
||||||
/// IDs of non-networked GameObjects.
|
/// This class represents a GameObject in FFXIV.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const uint InvalidGameObjectId = 0xE0000000;
|
public unsafe partial class GameObject : IEquatable<GameObject>
|
||||||
|
|
||||||
/// <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>
|
||||||
|
/// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the game object in memory.
|
/// This class represents a basic actor (GameObject) in FFXIV.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr Address { get; }
|
public unsafe partial class GameObject
|
||||||
|
|
||||||
/// <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.
|
/// <summary>
|
||||||
if (gameObject1 is null || gameObject2 is null)
|
/// Gets the name of this <see cref="GameObject" />.
|
||||||
return Equals(gameObject1, gameObject2);
|
/// </summary>
|
||||||
|
public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64);
|
||||||
|
|
||||||
return gameObject1.Equals(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}";
|
||||||
}
|
}
|
||||||
|
|
||||||
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,177 +7,178 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PartyList"/> class.
|
/// This collection represents the actors present in your party or alliance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">Client state address resolver.</param>
|
[PluginInterface]
|
||||||
internal PartyList(ClientStateAddressResolver addressResolver)
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed unsafe partial class PartyList
|
||||||
{
|
{
|
||||||
this.address = addressResolver;
|
private const int GroupLength = 8;
|
||||||
|
private const int AllianceLength = 20;
|
||||||
|
|
||||||
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
|
private readonly ClientStateAddressResolver address;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of party members the local player has.
|
/// Initializes a new instance of the <see cref="PartyList"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Length => this.GroupManagerStruct->MemberCount;
|
/// <param name="addressResolver">Client state address resolver.</param>
|
||||||
|
internal PartyList(ClientStateAddressResolver addressResolver)
|
||||||
/// <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.
|
this.address = addressResolver;
|
||||||
if (index < 0 || index >= this.Length)
|
|
||||||
|
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;
|
return null;
|
||||||
|
|
||||||
if (this.Length > GroupLength)
|
if (address == IntPtr.Zero)
|
||||||
{
|
return null;
|
||||||
var addr = this.GetAllianceMemberAddress(index);
|
|
||||||
return this.CreateAllianceMemberReference(addr);
|
return new PartyMember(address);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var addr = this.GetPartyMemberAddress(index);
|
|
||||||
return this.CreatePartyMemberReference(addr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the party member at the specified index of the party list.
|
/// Gets the address of the alliance member at the specified index of the alliance list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">The index of the party member.</param>
|
/// <param name="index">The index of the alliance member.</param>
|
||||||
/// <returns>The memory address of the party member.</returns>
|
/// <returns>The memory address of the alliance member.</returns>
|
||||||
public IntPtr GetPartyMemberAddress(int index)
|
public IntPtr GetAllianceMemberAddress(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 (index < 0 || index >= AllianceLength)
|
||||||
|
return IntPtr.Zero;
|
||||||
|
|
||||||
if (member == null)
|
return this.AllianceListAddress + (index * PartyMemberSize);
|
||||||
break;
|
}
|
||||||
|
|
||||||
yield return member;
|
/// <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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
/// 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,104 +9,105 @@ using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using JetBrains.Annotations;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
/// This class represents a party member in the group manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the party member.</param>
|
public unsafe class PartyMember
|
||||||
internal PartyMember(IntPtr address)
|
|
||||||
{
|
{
|
||||||
this.Address = 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,30 +1,31 @@
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Lumina.Excel;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ExcelResolver{T}"/> class.
|
/// This object resolves a rowID within an Excel sheet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ID of the classJob.</param>
|
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
|
||||||
internal ExcelResolver(uint id)
|
public class ExcelResolver<T> where T : ExcelRow
|
||||||
{
|
{
|
||||||
this.Id = 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,64 +4,65 @@ using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Status"/> class.
|
/// This class represents a status effect an actor is afflicted by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Status address.</param>
|
public unsafe class Status
|
||||||
internal Status(IntPtr address)
|
|
||||||
{
|
{
|
||||||
this.Address = 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,158 +3,159 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
/// This collection represents the status effects an actor is afflicted by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the status list.</param>
|
public sealed unsafe partial class StatusList
|
||||||
internal StatusList(IntPtr address)
|
|
||||||
{
|
{
|
||||||
this.Address = address;
|
private const int StatusListLength = 30;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pointer">Pointer to the status list.</param>
|
/// <param name="address">Address of the status list.</param>
|
||||||
internal unsafe StatusList(void* pointer)
|
internal StatusList(IntPtr address)
|
||||||
: 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)
|
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)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var addr = this.GetStatusAddress(index);
|
if (address == IntPtr.Zero)
|
||||||
return CreateStatusReference(addr);
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a reference to an FFXIV actor status list.
|
/// This collection represents the status effects an actor is afflicted by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of the status list in memory.</param>
|
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
||||||
/// <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
|
/// <inheritdoc/>
|
||||||
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
int IReadOnlyCollection<Status>.Count => this.Length;
|
||||||
// here or somewhere else.
|
|
||||||
var clientState = Service<ClientState>.Get();
|
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
/// <inheritdoc/>
|
||||||
return null;
|
int ICollection.Count => this.Length;
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
/// <inheritdoc/>
|
||||||
return null;
|
bool ICollection.IsSynchronized => false;
|
||||||
|
|
||||||
return new StatusList(address);
|
/// <inheritdoc/>
|
||||||
}
|
object ICollection.SyncRoot => this;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Create a reference to an FFXIV actor status.
|
public IEnumerator<Status> GetEnumerator()
|
||||||
/// </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++)
|
|
||||||
{
|
{
|
||||||
var status = this[i];
|
for (var i = 0; i < StatusListLength; i++)
|
||||||
|
{
|
||||||
|
var status = this[i];
|
||||||
|
|
||||||
if (status == null || status.StatusId == 0)
|
if (status == null || status.StatusId == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
yield return status;
|
yield return status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void ICollection.CopyTo(Array array, int index)
|
void ICollection.CopyTo(Array array, int index)
|
||||||
{
|
|
||||||
for (var i = 0; i < this.Length; i++)
|
|
||||||
{
|
{
|
||||||
array.SetValue(this[i], index);
|
for (var i = 0; i < this.Length; i++)
|
||||||
index++;
|
{
|
||||||
|
array.SetValue(this[i], index);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,36 @@
|
||||||
using System.Runtime.InteropServices;
|
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>
|
/// <summary>
|
||||||
/// The effect ID.
|
/// Native memory representation of a FFXIV status effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public short EffectId;
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct StatusEffect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The effect ID.
|
||||||
|
/// </summary>
|
||||||
|
public short EffectId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many stacks are present.
|
/// How many stacks are present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte StackCount;
|
public byte StackCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additional parameters.
|
/// Additional parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte Param;
|
public byte Param;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The duration remaining.
|
/// The duration remaining.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Duration;
|
public float Duration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the actor that caused this effect.
|
/// The ID of the actor that caused this effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int OwnerId;
|
public int OwnerId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,48 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Dalamud.Game.Command;
|
namespace Dalamud.Game.Command
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class describes a registered command.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CommandInfo
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CommandInfo"/> class.
|
/// This class describes a registered command.
|
||||||
/// Create a new CommandInfo with the provided handler.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handler">The method to call when the command is run.</param>
|
public sealed class CommandInfo
|
||||||
public CommandInfo(HandlerDelegate handler)
|
|
||||||
{
|
{
|
||||||
this.Handler = handler;
|
/// <summary>
|
||||||
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
/// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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,166 +10,167 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CommandManager"/> class.
|
/// This class manages registered in-game slash commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal CommandManager()
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed class CommandManager
|
||||||
{
|
{
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
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;
|
||||||
|
|
||||||
this.currentLangCommandRegex = startInfo.Language switch
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CommandManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal CommandManager()
|
||||||
{
|
{
|
||||||
ClientLanguage.Japanese => this.commandRegexJp,
|
var startInfo = Service<DalamudStartInfo>.Get();
|
||||||
ClientLanguage.English => this.commandRegexEn,
|
|
||||||
ClientLanguage.German => this.commandRegexDe,
|
|
||||||
ClientLanguage.French => this.commandRegexFr,
|
|
||||||
_ => this.currentLangCommandRegex,
|
|
||||||
};
|
|
||||||
|
|
||||||
Service<ChatGui>.Get().CheckMessageHandled += this.OnCheckMessageHandled;
|
this.currentLangCommandRegex = startInfo.Language switch
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// Remove the trailing space
|
ClientLanguage.Japanese => this.commandRegexJp,
|
||||||
command = content.Substring(0, separatorPosition);
|
ClientLanguage.English => this.commandRegexEn,
|
||||||
|
ClientLanguage.German => this.commandRegexDe,
|
||||||
|
ClientLanguage.French => this.commandRegexFr,
|
||||||
|
_ => this.currentLangCommandRegex,
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// Remove the trailing space
|
||||||
|
command = content.Substring(0, separatorPosition);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
command = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
argument = string.Empty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
command = content;
|
// 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..];
|
||||||
}
|
}
|
||||||
|
|
||||||
argument = string.Empty;
|
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
||||||
}
|
return false;
|
||||||
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;
|
this.DispatchCommand(command, argument, handler);
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
Log.Error("Command {CommandName} is already registered.", command);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a command from the command handlers.
|
/// Dispatch the handling of a command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="command">The command to remove.</param>
|
/// <param name="command">The command to dispatch.</param>
|
||||||
/// <returns>If the removal was successful.</returns>
|
/// <param name="argument">The provided arguments.</param>
|
||||||
public bool RemoveHandler(string command)
|
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
||||||
{
|
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||||
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"];
|
try
|
||||||
if (cmdMatch.Success)
|
|
||||||
{
|
{
|
||||||
// Yes, it's a chat command.
|
info.Handler(command, argument);
|
||||||
var command = cmdMatch.Value;
|
|
||||||
if (this.ProcessCommand(command)) isHandled = true;
|
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Always match for china, since they patch in language files without changing the ClientLanguage.
|
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
|
||||||
cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"];
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
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"];
|
||||||
if (cmdMatch.Success)
|
if (cmdMatch.Success)
|
||||||
{
|
{
|
||||||
// Yes, it's a Chinese fallback chat command.
|
// Yes, it's a chat command.
|
||||||
var command = cmdMatch.Value;
|
var command = cmdMatch.Value;
|
||||||
if (this.ProcessCommand(command)) isHandled = true;
|
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,300 +16,301 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Framework"/> class.
|
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Framework()
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed class Framework : IDisposable
|
||||||
{
|
{
|
||||||
this.Address = new FrameworkAddressResolver();
|
private static Stopwatch statsStopwatch = new();
|
||||||
this.Address.Setup();
|
private Stopwatch updateStopwatch = new();
|
||||||
|
|
||||||
Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}");
|
private bool tier2Initialized = false;
|
||||||
if (this.Address.BaseAddress == IntPtr.Zero)
|
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()
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook virtual functions
|
/// <summary>
|
||||||
this.HookVTable();
|
/// 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>
|
/// <summary>
|
||||||
/// A delegate type used with the <see cref="Update"/> event.
|
/// A delegate type used during the native Framework::destroy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="framework">The Framework instance.</param>
|
/// <param name="framework">The native Framework address.</param>
|
||||||
public delegate void OnUpdateDelegate(Framework framework);
|
/// <returns>A value indicating if the call was successful.</returns>
|
||||||
|
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A delegate type used during the native Framework::destroy.
|
/// A delegate type used during the native Framework::free.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="framework">The native Framework address.</param>
|
/// <returns>The native Framework address.</returns>
|
||||||
/// <returns>A value indicating if the call was successful.</returns>
|
public delegate IntPtr OnDestroyDelegate();
|
||||||
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
|
||||||
|
|
||||||
/// <summary>
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
/// A delegate type used during the native Framework::free.
|
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||||
/// </summary>
|
|
||||||
/// <returns>The native Framework address.</returns>
|
|
||||||
public delegate IntPtr OnDestroyDelegate();
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
|
||||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
|
||||||
|
|
||||||
private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
|
/// <summary>
|
||||||
|
/// Event that gets fired every time the game framework updates.
|
||||||
|
/// </summary>
|
||||||
|
public event OnUpdateDelegate Update;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that gets fired every time the game framework updates.
|
/// Gets or sets a value indicating whether the collection of stats is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnUpdateDelegate Update;
|
public static bool StatsEnabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the collection of stats is enabled.
|
/// Gets the stats history mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool StatsEnabled { get; set; }
|
public static Dictionary<string, List<double>> StatsHistory { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the stats history mapping.
|
/// Gets a raw pointer to the instance of Client::Framework.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, List<double>> StatsHistory { get; } = new();
|
public FrameworkAddressResolver Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a raw pointer to the instance of Client::Framework.
|
/// Gets the last time that the Framework Update event was triggered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FrameworkAddressResolver Address { get; }
|
public DateTime LastUpdate { get; private set; } = DateTime.MinValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the last time that the Framework Update event was triggered.
|
/// Gets the last time in UTC that the Framework Update event was triggered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime LastUpdate { get; private set; } = DateTime.MinValue;
|
public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the last time in UTC that the Framework Update event was triggered.
|
/// Gets the delta between the last Framework Update and the currently executing one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue;
|
public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the delta between the last Framework Update and the currently executing one.
|
/// Gets or sets a value indicating whether to dispatch update events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero;
|
internal bool DispatchUpdateEvents { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to dispatch update events.
|
/// Enable this module.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool DispatchUpdateEvents { get; set; } = true;
|
public void Enable()
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
this.tier2Initialized = dalamud.LoadTier2();
|
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)
|
if (!this.tier2Initialized)
|
||||||
this.tierInitError = true;
|
{
|
||||||
|
this.tier2Initialized = dalamud.LoadTier2();
|
||||||
|
if (!this.tier2Initialized)
|
||||||
|
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
|
// 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)
|
if (!this.tier3Initialized && Service<InterfaceManager>.GetNullable()?.IsReady == true)
|
||||||
{
|
{
|
||||||
this.tier3Initialized = dalamud.LoadTier3();
|
this.tier3Initialized = dalamud.LoadTier3();
|
||||||
if (!this.tier3Initialized)
|
if (!this.tier3Initialized)
|
||||||
this.tierInitError = true;
|
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
|
try
|
||||||
{
|
{
|
||||||
if (StatsEnabled && this.Update != null)
|
Service<ChatGui>.Get().UpdateQueue();
|
||||||
{
|
Service<ToastGui>.Get().UpdateQueue();
|
||||||
// Stat Tracking for Framework Updates
|
Service<GameNetwork>.Get().UpdateQueue();
|
||||||
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
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
|
||||||
|
{
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
original:
|
private bool HandleRealDestroy(IntPtr framework)
|
||||||
return this.updateHook.Original(framework);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HandleRealDestroy(IntPtr framework)
|
|
||||||
{
|
|
||||||
if (this.DispatchUpdateEvents)
|
|
||||||
{
|
{
|
||||||
Log.Information("Framework::Destroy!");
|
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);
|
||||||
|
|
||||||
var dalamud = Service<Dalamud>.Get();
|
var dalamud = Service<Dalamud>.Get();
|
||||||
dalamud.DisposePlugins();
|
dalamud.Unload();
|
||||||
|
dalamud.WaitForUnloadFinish();
|
||||||
|
|
||||||
Log.Information("Framework::Destroy OK!");
|
Log.Information("Framework::Free OK!");
|
||||||
|
|
||||||
|
// Return the original trampoline location to cleanly exit
|
||||||
|
return originalPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,54 +1,57 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
using Dalamud.Game.Internal;
|
||||||
|
|
||||||
/// <summary>
|
namespace Dalamud.Game
|
||||||
/// The address resolver for the <see cref="Framework"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class FrameworkAddressResolver : BaseAddressResolver
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base address native Framework class.
|
/// The address resolver for the <see cref="Framework"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr BaseAddress { get; private set; }
|
public sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
this.SetupFramework(sig);
|
/// <summary>
|
||||||
|
/// Gets the base address native Framework class.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr BaseAddress { get; private set; }
|
||||||
|
|
||||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
/// <summary>
|
||||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
/// Gets the address for the native GuiManager class.
|
||||||
this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08);
|
/// </summary>
|
||||||
|
public IntPtr GuiManager { get; private set; }
|
||||||
|
|
||||||
// Called from Framework::Init
|
/// <summary>
|
||||||
this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here
|
/// Gets the address for the native ScriptManager class.
|
||||||
}
|
/// </summary>
|
||||||
|
public IntPtr ScriptManager { get; private set; }
|
||||||
|
|
||||||
private void SetupFramework(SigScanner scanner)
|
/// <inheritdoc/>
|
||||||
{
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
// Dissasembly of part of the .dtor
|
{
|
||||||
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
this.SetupFramework(sig);
|
||||||
// 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.
|
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
||||||
this.BaseAddress = Marshal.ReadIntPtr(pFramework);
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,404 +5,405 @@ using System.Text;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="version">Version string to parse.</param>
|
[Serializable]
|
||||||
[JsonConstructor]
|
public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersion>, IEquatable<GameVersion>
|
||||||
public GameVersion(string version)
|
|
||||||
{
|
{
|
||||||
var ver = Parse(version);
|
private static readonly GameVersion AnyVersion = new();
|
||||||
this.Year = ver.Year;
|
|
||||||
this.Month = ver.Month;
|
|
||||||
this.Day = ver.Day;
|
|
||||||
this.Major = ver.Major;
|
|
||||||
this.Minor = ver.Minor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="year">The year.</param>
|
/// <param name="version">Version string to parse.</param>
|
||||||
/// <param name="month">The month.</param>
|
[JsonConstructor]
|
||||||
/// <param name="day">The day.</param>
|
public GameVersion(string version)
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
return v2 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 v1.Equals(v2);
|
/// <summary>
|
||||||
}
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
|
/// </summary>
|
||||||
public static bool operator !=(GameVersion v1, GameVersion v2)
|
/// <param name="year">The year.</param>
|
||||||
{
|
/// <param name="month">The month.</param>
|
||||||
return !(v1 == v2);
|
/// <param name="day">The day.</param>
|
||||||
}
|
/// <param name="major">The major version.</param>
|
||||||
|
/// <param name="minor">The minor version.</param>
|
||||||
public static bool operator <(GameVersion v1, GameVersion v2)
|
public GameVersion(int year, int month, int day, int major, int minor)
|
||||||
{
|
|
||||||
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);
|
if ((this.Year = year) < 0)
|
||||||
return (result, value);
|
throw new ArgumentOutOfRangeException(nameof(year));
|
||||||
}).ToArray();
|
|
||||||
|
|
||||||
if (tplParts.Any(t => !t.result))
|
if ((this.Month = month) < 0)
|
||||||
throw new FormatException("Bad formatting");
|
throw new ArgumentOutOfRangeException(nameof(month));
|
||||||
|
|
||||||
var intParts = tplParts.Select(t => t.value).ToArray();
|
if ((this.Day = day) < 0)
|
||||||
var len = intParts.Length;
|
throw new ArgumentOutOfRangeException(nameof(day));
|
||||||
|
|
||||||
if (len == 1)
|
if ((this.Major = major) < 0)
|
||||||
return new GameVersion(intParts[0]);
|
throw new ArgumentOutOfRangeException(nameof(major));
|
||||||
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>
|
if ((this.Minor = minor) < 0)
|
||||||
/// Try to parse a version string. YYYY.MM.DD.majr.minr or "any".
|
throw new ArgumentOutOfRangeException(nameof(minor));
|
||||||
/// </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
|
|
||||||
|
/// <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)
|
||||||
{
|
{
|
||||||
result = null;
|
if ((this.Year = year) < 0)
|
||||||
return false;
|
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));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
|
/// </summary>
|
||||||
/// <inheritdoc/>
|
/// <param name="year">The year.</param>
|
||||||
public int CompareTo(object obj)
|
/// <param name="month">The month.</param>
|
||||||
{
|
/// <param name="day">The day.</param>
|
||||||
if (obj == null)
|
public GameVersion(int year, int month, int day)
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (obj is GameVersion value)
|
|
||||||
{
|
{
|
||||||
return this.CompareTo(value);
|
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));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
/// <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)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Argument must be a GameVersion");
|
if ((this.Year = year) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(year));
|
||||||
|
|
||||||
|
if ((this.Month = month) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(month));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
public int CompareTo(GameVersion value)
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
{
|
/// </summary>
|
||||||
if (value == null)
|
/// <param name="year">The year.</param>
|
||||||
return 1;
|
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;
|
||||||
|
|
||||||
if (this == value)
|
|
||||||
return 0;
|
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
|
/// <inheritdoc/>
|
||||||
(this.Year == value.Year) &&
|
public override bool Equals(object obj)
|
||||||
(this.Month == value.Month) &&
|
{
|
||||||
(this.Day == value.Day) &&
|
if (obj is not GameVersion value)
|
||||||
(this.Major == value.Major) &&
|
return false;
|
||||||
(this.Minor == value.Minor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
return this.Equals(value);
|
||||||
public override int GetHashCode()
|
}
|
||||||
{
|
|
||||||
var accumulator = 0;
|
|
||||||
|
|
||||||
// This might be horribly wrong, but it isn't used heavily.
|
/// <inheritdoc/>
|
||||||
accumulator |= this.Year.GetHashCode();
|
public bool Equals(GameVersion value)
|
||||||
accumulator |= this.Month.GetHashCode();
|
{
|
||||||
accumulator |= this.Day.GetHashCode();
|
if (value == null)
|
||||||
accumulator |= this.Major.GetHashCode();
|
{
|
||||||
accumulator |= this.Minor.GetHashCode();
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return accumulator;
|
return
|
||||||
}
|
(this.Year == value.Year) &&
|
||||||
|
(this.Month == value.Month) &&
|
||||||
|
(this.Day == value.Day) &&
|
||||||
|
(this.Major == value.Major) &&
|
||||||
|
(this.Minor == value.Minor);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string ToString()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
if (this.Year == -1 &&
|
var accumulator = 0;
|
||||||
this.Month == -1 &&
|
|
||||||
this.Day == -1 &&
|
|
||||||
this.Major == -1 &&
|
|
||||||
this.Minor == -1)
|
|
||||||
return "any";
|
|
||||||
|
|
||||||
return new StringBuilder()
|
// This might be horribly wrong, but it isn't used heavily.
|
||||||
.Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year))
|
accumulator |= this.Year.GetHashCode();
|
||||||
.Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month))
|
accumulator |= this.Month.GetHashCode();
|
||||||
.Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day))
|
accumulator |= this.Day.GetHashCode();
|
||||||
.Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major))
|
accumulator |= this.Major.GetHashCode();
|
||||||
.Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor))
|
accumulator |= this.Minor.GetHashCode();
|
||||||
.ToString();
|
|
||||||
|
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,78 +2,79 @@ using System;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
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>
|
/// <summary>
|
||||||
/// Writes the JSON representation of the object.
|
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
|
public sealed class GameVersionConverter : JsonConverter
|
||||||
/// <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)
|
/// <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)
|
||||||
{
|
{
|
||||||
writer.WriteNull();
|
if (value == null)
|
||||||
}
|
|
||||||
else if (value is GameVersion)
|
|
||||||
{
|
|
||||||
writer.WriteValue(value.ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
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
|
writer.WriteNull();
|
||||||
{
|
}
|
||||||
return new GameVersion((string)reader.Value!);
|
else if (value is GameVersion)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
writer.WriteValue(value.ToString());
|
||||||
{
|
|
||||||
throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}");
|
throw new JsonSerializationException("Expected GameVersion object value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether this instance can convert the specified object type.
|
/// Reads the JSON representation of the object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="objectType">Type of the object.</param>
|
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
|
||||||
/// <returns>
|
/// <param name="objectType">Type of the object.</param>
|
||||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
|
||||||
/// </returns>
|
/// <param name="serializer">The calling serializer.</param>
|
||||||
public override bool CanConvert(Type objectType)
|
/// <returns>The object value.</returns>
|
||||||
{
|
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
return objectType == typeof(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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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,470 +13,471 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ChatGui"/> class.
|
/// This class handles interacting with the native chat UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseAddress">The base address of the ChatManager.</param>
|
[PluginInterface]
|
||||||
internal ChatGui(IntPtr baseAddress)
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed class ChatGui : IDisposable
|
||||||
{
|
{
|
||||||
this.address = new ChatGuiAddressResolver(baseAddress);
|
private readonly ChatGuiAddressResolver address;
|
||||||
this.address.Setup();
|
|
||||||
|
|
||||||
Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}");
|
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||||
|
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||||
|
|
||||||
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||||
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||||
this.interactableLinkClickedHook = new Hook<InteractableLinkClickedDelegate>(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
private IntPtr baseAddress = IntPtr.Zero;
|
||||||
/// 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>
|
/// <summary>
|
||||||
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
/// Initializes a new instance of the <see cref="ChatGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">The type of chat.</param>
|
/// <param name="baseAddress">The base address of the ChatManager.</param>
|
||||||
/// <param name="senderId">The sender ID.</param>
|
internal ChatGui(IntPtr baseAddress)
|
||||||
/// <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
|
|
||||||
{
|
{
|
||||||
Message = message,
|
this.address = new ChatGuiAddressResolver(baseAddress);
|
||||||
Type = configuration.GeneralChatType,
|
this.address.Setup();
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}");
|
||||||
/// 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.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||||
this.PrintChat(new XivChatEntry
|
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||||
{
|
this.interactableLinkClickedHook = new Hook<InteractableLinkClickedDelegate>(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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))
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
/// <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);
|
||||||
|
|
||||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
/// <summary>
|
||||||
{
|
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
||||||
try
|
/// </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()
|
||||||
{
|
{
|
||||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
this.printMessageHook.Enable();
|
||||||
|
this.populateItemLinkHook.Enable();
|
||||||
|
this.interactableLinkClickedHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
/// <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.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
Message = message,
|
||||||
return;
|
Type = configuration.GeneralChatType,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
/// <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();
|
||||||
|
|
||||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||||
var messageSize = 0;
|
this.PrintChat(new XivChatEntry
|
||||||
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)))
|
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)
|
||||||
{
|
{
|
||||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
continue;
|
||||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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))
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||||
|
{
|
||||||
|
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)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,120 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui;
|
using Dalamud.Game.Internal;
|
||||||
|
|
||||||
/// <summary>
|
namespace Dalamud.Game.Gui
|
||||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ChatGuiAddressResolver"/> class.
|
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseAddress">The base address of the native ChatManager class.</param>
|
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||||
public ChatGuiAddressResolver(IntPtr baseAddress)
|
|
||||||
{
|
{
|
||||||
this.BaseAddress = 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base address of the native ChatManager class.
|
/// Gets the base address of the native ChatManager class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr BaseAddress { get; }
|
public IntPtr BaseAddress { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native PrintMessage method.
|
/// Gets the address of the native PrintMessage method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr PrintMessage { get; private set; }
|
public IntPtr PrintMessage { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native PopulateItemLinkObject method.
|
/// Gets the address of the native PopulateItemLinkObject method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native InteractableLinkClicked method.
|
/// Gets the address of the native InteractableLinkClicked method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr InteractableLinkClicked { get; private set; }
|
public IntPtr InteractableLinkClicked { get; private set; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
--- for reference: 4.57 ---
|
--- 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 ; __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 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||||
.text:00000001405CD210
|
.text:00000001405CD210
|
||||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||||
.text:00000001405CD210 var_210 = word ptr -210h
|
.text:00000001405CD210 var_210 = word ptr -210h
|
||||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||||
.text:00000001405CD210 var_200 = word ptr -200h
|
.text:00000001405CD210 var_200 = word ptr -200h
|
||||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||||
.text:00000001405CD210
|
.text:00000001405CD210
|
||||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||||
.text:00000001405CD210 push rbp
|
.text:00000001405CD210 push rbp
|
||||||
.text:00000001405CD212 push rdi
|
.text:00000001405CD212 push rdi
|
||||||
.text:00000001405CD213 push r14
|
.text:00000001405CD213 push r14
|
||||||
.text:00000001405CD215 push r15
|
.text:00000001405CD215 push r15
|
||||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||||
.text:00000001405CD21F sub rsp, 228h
|
.text:00000001405CD21F sub rsp, 228h
|
||||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||||
.text:00000001405CD22D xor rax, rsp
|
.text:00000001405CD22D xor rax, rsp
|
||||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||||
.text:00000001405CD237 xor r10b, r10b
|
.text:00000001405CD237 xor r10b, r10b
|
||||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||||
.text:00000001405CD23F xor eax, eax
|
.text:00000001405CD23F xor eax, eax
|
||||||
.text:00000001405CD241 mov r11, r9
|
.text:00000001405CD241 mov r11, r9
|
||||||
.text:00000001405CD244 mov r14, r8
|
.text:00000001405CD244 mov r14, r8
|
||||||
.text:00000001405CD247 mov r9d, eax
|
.text:00000001405CD247 mov r9d, eax
|
||||||
.text:00000001405CD24A movzx r15d, dx
|
.text:00000001405CD24A movzx r15d, dx
|
||||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||||
.text:00000001405CD255 mov rdi, rcx
|
.text:00000001405CD255 mov rdi, rcx
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
// PrintMessage = sig.ScanText("4055 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");
|
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("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
|
// 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.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,302 +9,303 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
/// This class facilitates interacting with and creating native in-game "fly text".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly AddFlyTextDelegate addFlyTextNative;
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
/// <summary>
|
public sealed class FlyTextGui : IDisposable
|
||||||
/// 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()
|
|
||||||
{
|
{
|
||||||
this.Address = new FlyTextGuiAddressResolver();
|
/// <summary>
|
||||||
this.Address.Setup();
|
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||||
|
/// </summary>
|
||||||
|
private readonly AddFlyTextDelegate addFlyTextNative;
|
||||||
|
|
||||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
/// <summary>
|
||||||
this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
||||||
}
|
/// </summary>
|
||||||
|
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The delegate defining the type for the FlyText event.
|
/// Initializes a new instance of the <see cref="FlyTextGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
internal FlyTextGui()
|
||||||
/// <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())
|
this.Address = new FlyTextGuiAddressResolver();
|
||||||
{
|
this.Address.Setup();
|
||||||
strArray->StringArray[strOffset + 0] = pText1;
|
|
||||||
strArray->StringArray[strOffset + 1] = pText2;
|
|
||||||
|
|
||||||
this.addFlyTextNative(
|
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||||
flytext,
|
this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||||
actorIndex,
|
|
||||||
1,
|
|
||||||
(IntPtr)numArray,
|
|
||||||
numOffset,
|
|
||||||
9,
|
|
||||||
(IntPtr)strArray,
|
|
||||||
strOffset,
|
|
||||||
2,
|
|
||||||
0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables this module.
|
/// The delegate defining the type for the FlyText event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void Enable()
|
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||||
{
|
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||||
this.createFlyTextHook.Enable();
|
/// <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);
|
||||||
|
|
||||||
private static byte[] Terminate(byte[] source)
|
/// <summary>
|
||||||
{
|
/// Private delegate for the native CreateFlyText function's hook.
|
||||||
var terminated = new byte[source.Length + 1];
|
/// </summary>
|
||||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
private delegate IntPtr CreateFlyTextDelegate(
|
||||||
terminated[^1] = 0;
|
IntPtr addonFlyText,
|
||||||
|
FlyTextKind kind,
|
||||||
|
int val1,
|
||||||
|
int val2,
|
||||||
|
IntPtr text2,
|
||||||
|
uint color,
|
||||||
|
uint icon,
|
||||||
|
IntPtr text1,
|
||||||
|
float yOffset);
|
||||||
|
|
||||||
return terminated;
|
/// <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);
|
||||||
|
|
||||||
private IntPtr CreateFlyTextDetour(
|
/// <summary>
|
||||||
IntPtr addonFlyText,
|
/// The FlyText event that can be subscribed to.
|
||||||
FlyTextKind kind,
|
/// </summary>
|
||||||
int val1,
|
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||||
int val2,
|
|
||||||
IntPtr text2,
|
private Dalamud Dalamud { get; }
|
||||||
uint color,
|
|
||||||
uint icon,
|
private FlyTextGuiAddressResolver Address { get; }
|
||||||
IntPtr text1,
|
|
||||||
float yOffset)
|
/// <summary>
|
||||||
{
|
/// Disposes of managed and unmanaged resources.
|
||||||
var retVal = IntPtr.Zero;
|
/// </summary>
|
||||||
try
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
this.createFlyTextHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
var handled = false;
|
/// <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;
|
||||||
|
|
||||||
var tmpKind = kind;
|
// Get the UI module and flytext addon pointers
|
||||||
var tmpVal1 = val1;
|
var gameGui = Service<GameGui>.Get();
|
||||||
var tmpVal2 = val2;
|
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
var flytext = gameGui.GetAddonByName("_FlyText", 1);
|
||||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
|
||||||
var tmpColor = color;
|
|
||||||
var tmpIcon = icon;
|
|
||||||
var tmpYOffset = yOffset;
|
|
||||||
|
|
||||||
var cmpText1 = tmpText1.ToString();
|
if (ui == null || flytext == IntPtr.Zero)
|
||||||
var cmpText2 = tmpText2.ToString();
|
return;
|
||||||
|
|
||||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
// Get the number and string arrays we need
|
||||||
$"kind({kind}) val1({val1}) val2({val2}) " +
|
var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder;
|
||||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||||
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
|
// Write the values to the arrays using a known valid flytext region
|
||||||
if (handled)
|
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())
|
||||||
{
|
{
|
||||||
Log.Verbose("[FlyText] FlyText was handled.");
|
fixed (byte* pText2 = text2.Encode())
|
||||||
|
|
||||||
// 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);
|
strArray->StringArray[strOffset + 0] = pText1;
|
||||||
Marshal.FreeHGlobal(pText2);
|
strArray->StringArray[strOffset + 1] = pText2;
|
||||||
Log.Verbose("[FlyText] Freed strings.");
|
|
||||||
|
this.addFlyTextNative(
|
||||||
|
flytext,
|
||||||
|
actorIndex,
|
||||||
|
1,
|
||||||
|
(IntPtr)numArray,
|
||||||
|
numOffset,
|
||||||
|
9,
|
||||||
|
(IntPtr)strArray,
|
||||||
|
strOffset,
|
||||||
|
2,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
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,31 +1,32 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Gets the address of the native AddFlyText method, which occurs
|
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||||
/// when the game adds fly text elements to the UI. Multiple fly text
|
|
||||||
/// elements can be added in a single AddFlyText call.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr AddFlyText { get; private set; }
|
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||||
|
|
||||||
/// <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");
|
/// <summary>
|
||||||
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
|
/// 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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,282 +1,283 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
/// Enum of FlyTextKind values. Members suffixed with
|
||||||
/// Used for autos and incoming DoTs.
|
/// a number seem to be a duplicate, or perform duplicate behavior.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttack = 0,
|
public enum FlyTextKind : int
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||||
|
/// Used for autos and incoming DoTs.
|
||||||
|
/// </summary>
|
||||||
|
AutoAttack = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||||
/// Does a bounce effect on appearance.
|
/// Does a bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DirectHit = 1,
|
DirectHit = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||||
/// Does a bigger bounce effect on appearance.
|
/// Does a bigger bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit = 2,
|
CriticalHit = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
||||||
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
||||||
/// Does not scroll up or down the screen.
|
/// Does not scroll up or down the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalDirectHit = 3,
|
CriticalDirectHit = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedAttack = 4,
|
NamedAttack = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedDirectHit = 5,
|
NamedDirectHit = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHit = 6,
|
NamedCriticalHit = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalDirectHit = 7,
|
NamedCriticalDirectHit = 7,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps, serif MISS.
|
/// All caps, serif MISS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Miss = 8,
|
Miss = 8,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to all caps serif MISS.
|
/// Sans-serif Text1 next to all caps serif MISS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMiss = 9,
|
NamedMiss = 9,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif DODGE.
|
/// All caps serif DODGE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Dodge = 10,
|
Dodge = 10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to all caps serif DODGE.
|
/// Sans-serif Text1 next to all caps serif DODGE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedDodge = 11,
|
NamedDodge = 11,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Icon next to sans-serif Text1.
|
/// Icon next to sans-serif Text1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIcon = 12,
|
NamedIcon = 12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Icon next to sans-serif Text1 (2).
|
/// Icon next to sans-serif Text1 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIcon2 = 13,
|
NamedIcon2 = 13,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Exp = 14,
|
Exp = 14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMp = 15,
|
NamedMp = 15,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedTp = 16,
|
NamedTp = 16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedAttack2 = 17,
|
NamedAttack2 = 17,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMp2 = 18,
|
NamedMp2 = 18,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedTp2 = 19,
|
NamedTp2 = 19,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedEp = 20,
|
NamedEp = 20,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Displays nothing.
|
/// Displays nothing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 21,
|
None = 21,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif INVULNERABLE.
|
/// All caps serif INVULNERABLE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Invulnerable = 22,
|
Invulnerable = 22,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps sans-serif condensed font INTERRUPTED!
|
/// All caps sans-serif condensed font INTERRUPTED!
|
||||||
/// Does a large bounce effect on appearance.
|
/// Does a large bounce effect on appearance.
|
||||||
/// Does not scroll up or down the screen.
|
/// Does not scroll up or down the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Interrupted = 23,
|
Interrupted = 23,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2.
|
/// AutoAttack with no Text2.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText = 24,
|
AutoAttackNoText = 24,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2 (2).
|
/// AutoAttack with no Text2 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText2 = 25,
|
AutoAttackNoText2 = 25,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit2 = 26,
|
CriticalHit2 = 26,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2 (3).
|
/// AutoAttack with no Text2 (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText3 = 27,
|
AutoAttackNoText3 = 27,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHit2 = 28,
|
NamedCriticalHit2 = 28,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
/// 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.
|
/// Does a jiggle effect to the right on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHitWithMp = 29,
|
NamedCriticalHitWithMp = 29,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
/// 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.
|
/// Does a jiggle effect to the right on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHitWithTp = 30,
|
NamedCriticalHitWithTp = 30,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconHasNoEffect = 31,
|
NamedIconHasNoEffect = 31,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconFaded = 32,
|
NamedIconFaded = 32,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon but Text1 is slightly faded (2).
|
/// Same as NamedIcon but Text1 is slightly faded (2).
|
||||||
/// Used for buff expiration.
|
/// Used for buff expiration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconFaded2 = 33,
|
NamedIconFaded2 = 33,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text1 in sans-serif font.
|
/// Text1 in sans-serif font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Named = 34,
|
Named = 34,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconFullyResisted = 35,
|
NamedIconFullyResisted = 35,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif 'INCAPACITATED!'.
|
/// All caps serif 'INCAPACITATED!'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Incapacitated = 36,
|
Incapacitated = 36,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedFullyResisted = 37,
|
NamedFullyResisted = 37,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text1 with sans-serif "has no effect!" to the right.
|
/// Text1 with sans-serif "has no effect!" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedHasNoEffect = 38,
|
NamedHasNoEffect = 38,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedAttack3 = 39,
|
NamedAttack3 = 39,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMp3 = 40,
|
NamedMp3 = 40,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedTp3 = 41,
|
NamedTp3 = 41,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconInvulnerable = 42,
|
NamedIconInvulnerable = 42,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif RESIST.
|
/// All caps serif RESIST.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Resist = 43,
|
Resist = 43,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon but places the given icon in the item icon outline.
|
/// Same as NamedIcon but places the given icon in the item icon outline.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconWithItemOutline = 44,
|
NamedIconWithItemOutline = 44,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2 (4).
|
/// AutoAttack with no Text2 (4).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText4 = 45,
|
AutoAttackNoText4 = 45,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
||||||
/// Does a bigger bounce effect on appearance.
|
/// Does a bigger bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit3 = 46,
|
CriticalHit3 = 46,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif REFLECT.
|
/// All caps serif REFLECT.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Reflect = 47,
|
Reflect = 47,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif REFLECTED.
|
/// All caps serif REFLECTED.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Reflected = 48,
|
Reflected = 48,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
||||||
/// Does a bounce effect on appearance.
|
/// Does a bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DirectHit2 = 49,
|
DirectHit2 = 49,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
||||||
/// Does a bigger bounce effect on appearance.
|
/// Does a bigger bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit4 = 50,
|
CriticalHit4 = 50,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
/// 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.
|
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalDirectHit2 = 51,
|
CriticalDirectHit2 = 51,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,103 +1,104 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameGuiAddressResolver"/> class.
|
/// The address resolver for the <see cref="GameGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameGuiAddressResolver()
|
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
this.BaseAddress = Service<Framework>.Get().Address.BaseAddress;
|
/// <summary>
|
||||||
}
|
/// Initializes a new instance of the <see cref="GameGuiAddressResolver"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public GameGuiAddressResolver()
|
||||||
|
{
|
||||||
|
this.BaseAddress = Service<Framework>.Get().Address.BaseAddress;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base address of the native GuiManager class.
|
/// Gets the base address of the native GuiManager class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr BaseAddress { get; private set; }
|
public IntPtr BaseAddress { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native ChatManager class.
|
/// Gets the address of the native ChatManager class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr ChatManager { get; private set; }
|
public IntPtr ChatManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native SetGlobalBgm method.
|
/// Gets the address of the native SetGlobalBgm method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr SetGlobalBgm { get; private set; }
|
public IntPtr SetGlobalBgm { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native HandleItemHover method.
|
/// Gets the address of the native HandleItemHover method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr HandleItemHover { get; private set; }
|
public IntPtr HandleItemHover { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native HandleItemOut method.
|
/// Gets the address of the native HandleItemOut method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr HandleItemOut { get; private set; }
|
public IntPtr HandleItemOut { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native HandleActionHover method.
|
/// Gets the address of the native HandleActionHover method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr HandleActionHover { get; private set; }
|
public IntPtr HandleActionHover { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native HandleActionOut method.
|
/// Gets the address of the native HandleActionOut method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr HandleActionOut { get; private set; }
|
public IntPtr HandleActionOut { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native HandleImm method.
|
/// Gets the address of the native HandleImm method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr HandleImm { get; private set; }
|
public IntPtr HandleImm { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native GetMatrixSingleton method.
|
/// Gets the address of the native GetMatrixSingleton method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr GetMatrixSingleton { get; private set; }
|
public IntPtr GetMatrixSingleton { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native ScreenToWorld method.
|
/// Gets the address of the native ScreenToWorld method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr ScreenToWorld { get; private set; }
|
public IntPtr ScreenToWorld { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native ToggleUiHide method.
|
/// Gets the address of the native ToggleUiHide method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr ToggleUiHide { get; private set; }
|
public IntPtr ToggleUiHide { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the native GetAgentModule method.
|
/// Gets the address of the native GetAgentModule method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr GetAgentModule { get; private set; }
|
public IntPtr GetAgentModule { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
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.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.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.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.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.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.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.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
this.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");
|
var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28");
|
||||||
this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size);
|
this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void SetupInternal(SigScanner scanner)
|
protected override void SetupInternal(SigScanner scanner)
|
||||||
{
|
{
|
||||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
||||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
// Xiv__UiManager__GetChatManager+7 000 retn
|
||||||
this.ChatManager = this.BaseAddress + 0x13E0;
|
this.ChatManager = this.BaseAddress + 0x13E0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,49 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// No action is hovered.
|
/// ActionKinds used in AgentActionDetail.
|
||||||
|
/// These describe the possible kinds of actions being hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
public enum HoverActionKind
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No action is hovered.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A regular action is hovered.
|
/// A regular action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Action = 21,
|
Action = 21,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A general action is hovered.
|
/// A general action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GeneralAction = 23,
|
GeneralAction = 23,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A companion order type of action is hovered.
|
/// A companion order type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CompanionOrder = 24,
|
CompanionOrder = 24,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A main command type of action is hovered.
|
/// A main command type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MainCommand = 25,
|
MainCommand = 25,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An extras command type of action is hovered.
|
/// An extras command type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ExtraCommand = 26,
|
ExtraCommand = 26,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A pet order type of action is hovered.
|
/// A pet order type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PetOrder = 28,
|
PetOrder = 28,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A trait is hovered.
|
/// A trait is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Trait = 29,
|
Trait = 29,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// Gets or sets the base action ID.
|
/// This class represents the hotbar action currently hovered over by the cursor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint BaseActionID { get; set; } = 0;
|
public class HoveredAction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the base action ID.
|
||||||
|
/// </summary>
|
||||||
|
public uint BaseActionID { get; set; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the action ID accounting for automatic upgrades.
|
/// Gets or sets the action ID accounting for automatic upgrades.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint ActionID { get; set; } = 0;
|
public uint ActionID { get; set; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of action.
|
/// Gets or sets the type of action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
|
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,234 +9,235 @@ using ImGuiNET;
|
||||||
|
|
||||||
using static Dalamud.NativeFunctions;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
/// This class handles IME for non-English users.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal DalamudIME()
|
internal class DalamudIME : IDisposable
|
||||||
{
|
{
|
||||||
}
|
private static readonly ModuleLog Log = new("IME");
|
||||||
|
|
||||||
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
|
private IntPtr interfaceHandle;
|
||||||
|
private IntPtr wndProcPtr;
|
||||||
|
private IntPtr oldWndProcPtr;
|
||||||
|
private WndProcDelegate wndProcDelegate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the module is enabled.
|
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool IsEnabled { get; private set; }
|
internal DalamudIME()
|
||||||
|
|
||||||
/// <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>
|
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
|
||||||
/// 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)
|
/// <summary>
|
||||||
{
|
/// Gets a value indicating whether the module is enabled.
|
||||||
if (visible)
|
/// </summary>
|
||||||
Service<DalamudInterface>.Get().OpenIMEWindow();
|
internal bool IsEnabled { get; private set; }
|
||||||
else
|
|
||||||
Service<DalamudInterface>.Get().CloseIMEWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
|
/// <summary>
|
||||||
{
|
/// Gets the index of the first imm candidate in relation to the full list.
|
||||||
try
|
/// </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 (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
if (this.oldWndProcPtr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
|
||||||
var wmsg = (WindowsMessage)msg;
|
this.oldWndProcPtr = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (wmsg)
|
/// <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)
|
||||||
{
|
{
|
||||||
case WindowsMessage.WM_IME_NOTIFY:
|
var io = ImGui.GetIO();
|
||||||
switch ((IMECommand)wParam)
|
var wmsg = (WindowsMessage)msg;
|
||||||
{
|
|
||||||
case IMECommand.ChangeCandidate:
|
|
||||||
this.ToggleWindow(true);
|
|
||||||
|
|
||||||
if (hWnd == IntPtr.Zero)
|
switch (wmsg)
|
||||||
return 0;
|
{
|
||||||
|
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);
|
var hIMC = ImmGetContext(hWnd);
|
||||||
if (hIMC == IntPtr.Zero)
|
if (hIMC == IntPtr.Zero)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
|
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
||||||
if (size == 0)
|
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||||
break;
|
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
||||||
|
|
||||||
var candlistPtr = Marshal.AllocHGlobal((int)size);
|
var bytes = new byte[dwSize];
|
||||||
size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size);
|
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||||
|
Marshal.FreeHGlobal(unmanagedPointer);
|
||||||
|
|
||||||
var candlist = this.ImmCandNative = Marshal.PtrToStructure<CandidateList>(candlistPtr);
|
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||||
var pageSize = candlist.PageSize;
|
io.AddInputCharactersUTF8(lpstr);
|
||||||
var candCount = candlist.Count;
|
|
||||||
|
|
||||||
if (pageSize > 0 && candCount > 1)
|
this.ImmComp = string.Empty;
|
||||||
{
|
|
||||||
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.ImmCandNative = default;
|
||||||
this.ImmCand.Clear();
|
this.ImmCand.Clear();
|
||||||
break;
|
|
||||||
|
|
||||||
case IMECommand.CloseCandidate:
|
|
||||||
this.ToggleWindow(false);
|
this.ToggleWindow(false);
|
||||||
this.ImmCandNative = default;
|
}
|
||||||
this.ImmCand.Clear();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
||||||
break;
|
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0)
|
||||||
}
|
{
|
||||||
|
var hIMC = ImmGetContext(hWnd);
|
||||||
|
if (hIMC == IntPtr.Zero)
|
||||||
|
return 0;
|
||||||
|
|
||||||
break;
|
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
||||||
case WindowsMessage.WM_IME_COMPOSITION:
|
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||||
if ((lParam & (long)IMEComposition.ResultStr) > 0)
|
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
|
||||||
{
|
|
||||||
var hIMC = ImmGetContext(hWnd);
|
|
||||||
if (hIMC == IntPtr.Zero)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
var bytes = new byte[dwSize];
|
||||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||||
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
Marshal.FreeHGlobal(unmanagedPointer);
|
||||||
|
|
||||||
var bytes = new byte[dwSize];
|
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
this.ImmComp = lpstr;
|
||||||
Marshal.FreeHGlobal(unmanagedPointer);
|
if (lpstr == string.Empty)
|
||||||
|
this.ToggleWindow(false);
|
||||||
|
}
|
||||||
|
|
||||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
break;
|
||||||
io.AddInputCharactersUTF8(lpstr);
|
|
||||||
|
|
||||||
this.ImmComp = string.Empty;
|
default:
|
||||||
this.ImmCandNative = default;
|
break;
|
||||||
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)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
Log.Error(ex, "Prevented a crash in an IME hook");
|
||||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
|
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
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>
|
/// <summary>
|
||||||
/// Gets the size of this packet.
|
/// The structure of the PartyFinder packet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static int PacketSize { get; } = Marshal.SizeOf<PartyFinderPacket>();
|
[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 readonly int BatchNumber;
|
internal readonly int BatchNumber;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
private readonly byte[] padding1;
|
private readonly byte[] padding1;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||||
internal readonly PartyFinderPacketListing[] Listings;
|
internal readonly PartyFinderPacketListing[] Listings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,97 +2,98 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
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
|
|
||||||
{
|
{
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
/// <summary>
|
||||||
private readonly byte[] header1;
|
/// The structure of an individual listing within a packet.
|
||||||
internal readonly uint Id;
|
/// </summary>
|
||||||
|
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||||
private readonly byte[] header2;
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal readonly struct PartyFinderPacketListing
|
||||||
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
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||||
return this.Slots.All(slot => slot == 0);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// Gets the address of the native ReceiveListing method.
|
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr ReceiveListing { get; private set; }
|
public class PartyFinderAddressResolver : BaseAddressResolver
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
|
||||||
{
|
{
|
||||||
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
/// <summary>
|
||||||
|
/// Gets the address of the native ReceiveListing method.
|
||||||
|
/// </summary>
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,132 +8,133 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
/// This class handles interacting with the native PartyFinder window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal PartyFinderGui()
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
public sealed class PartyFinderGui : IDisposable
|
||||||
{
|
{
|
||||||
this.address = new PartyFinderAddressResolver();
|
private readonly PartyFinderAddressResolver address;
|
||||||
this.address.Setup();
|
private readonly IntPtr memory;
|
||||||
|
|
||||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
||||||
|
|
||||||
this.receiveListingHook = new Hook<ReceiveListingDelegate>(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
/// <summary>
|
||||||
}
|
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
internal PartyFinderGui()
|
||||||
/// 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
|
|
||||||
{
|
{
|
||||||
Marshal.FreeHGlobal(this.memory);
|
this.address = new PartyFinderAddressResolver();
|
||||||
}
|
this.address.Setup();
|
||||||
catch (BadImageFormatException)
|
|
||||||
{
|
|
||||||
Log.Warning("Could not free PartyFinderGui memory.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||||
{
|
|
||||||
try
|
this.receiveListingHook = new Hook<ReceiveListingDelegate>(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||||
{
|
|
||||||
this.HandleListingEvents(data);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.receiveListingHook.Original(managerPtr, data);
|
/// <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);
|
||||||
|
|
||||||
private void HandleListingEvents(IntPtr data)
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
{
|
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
||||||
var dataPtr = data + 0x10;
|
|
||||||
|
|
||||||
var packet = Marshal.PtrToStructure<PartyFinderPacket>(dataPtr);
|
/// <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;
|
||||||
|
|
||||||
// rewriting is an expensive operation, so only do it if necessary
|
/// <summary>
|
||||||
var needToRewrite = false;
|
/// Enables this module.
|
||||||
|
/// </summary>
|
||||||
for (var i = 0; i < packet.Listings.Length; i++)
|
public void Enable()
|
||||||
{
|
{
|
||||||
// these are empty slots that are not shown to the player
|
this.receiveListingHook.Enable();
|
||||||
if (packet.Listings[i].IsNull())
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of m anaged and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.receiveListingHook.Dispose();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
continue;
|
Marshal.FreeHGlobal(this.memory);
|
||||||
|
}
|
||||||
|
catch (BadImageFormatException)
|
||||||
|
{
|
||||||
|
Log.Warning("Could not free PartyFinderGui memory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.HandleListingEvents(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var listing = new PartyFinderListing(packet.Listings[i]);
|
this.receiveListingHook.Original(managerPtr, data);
|
||||||
var args = new PartyFinderListingEventArgs(packet.BatchNumber);
|
}
|
||||||
this.ReceiveListing?.Invoke(listing, args);
|
|
||||||
|
|
||||||
if (args.Visible)
|
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++)
|
||||||
{
|
{
|
||||||
continue;
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide the listing from the player by setting it to a null listing
|
if (!needToRewrite)
|
||||||
packet.Listings[i] = default;
|
{
|
||||||
needToRewrite = true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needToRewrite)
|
// write our struct into the memory (doing this directly crashes the game)
|
||||||
{
|
Marshal.StructureToPtr(packet, this.memory, false);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// write our struct into the memory (doing this directly crashes the game)
|
// copy our new memory over the game's
|
||||||
Marshal.StructureToPtr(packet, this.memory, false);
|
unsafe
|
||||||
|
{
|
||||||
// copy our new memory over the game's
|
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize);
|
||||||
unsafe
|
}
|
||||||
{
|
|
||||||
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// No duty condition.
|
/// Condition flags for the <see cref="PartyFinderGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 1,
|
[Flags]
|
||||||
|
public enum ConditionFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No duty condition.
|
||||||
|
/// </summary>
|
||||||
|
None = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The duty complete condition.
|
/// The duty complete condition.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DutyComplete = 2,
|
DutyComplete = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The duty incomplete condition.
|
/// The duty incomplete condition.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DutyIncomplete = 4,
|
DutyIncomplete = 4,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,48 @@
|
||||||
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>
|
/// <summary>
|
||||||
/// The duty category.
|
/// Category flags for the <see cref="PartyFinderGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Duty = 0,
|
public enum DutyCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The duty category.
|
||||||
|
/// </summary>
|
||||||
|
Duty = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The quest battle category.
|
/// The quest battle category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
QuestBattles = 1 << 0,
|
QuestBattles = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The fate category.
|
/// The fate category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Fates = 1 << 1,
|
Fates = 1 << 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The treasure hunt category.
|
/// The treasure hunt category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TreasureHunt = 1 << 2,
|
TreasureHunt = 1 << 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hunt category.
|
/// The hunt category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TheHunt = 1 << 3,
|
TheHunt = 1 << 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The gathering forays category.
|
/// The gathering forays category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GatheringForays = 1 << 4,
|
GatheringForays = 1 << 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The deep dungeons category.
|
/// The deep dungeons category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DeepDungeons = 1 << 5,
|
DeepDungeons = 1 << 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The adventuring forays category.
|
/// The adventuring forays category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AdventuringForays = 1 << 6,
|
AdventuringForays = 1 << 6,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,31 @@
|
||||||
using System;
|
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>
|
/// <summary>
|
||||||
/// No duty finder settings.
|
/// Duty finder settings flags for the <see cref="PartyFinderGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
[Flags]
|
||||||
|
public enum DutyFinderSettingsFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No duty finder settings.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The undersized party setting.
|
/// The undersized party setting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UndersizedParty = 1 << 0,
|
UndersizedParty = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum item level setting.
|
/// The minimum item level setting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MinimumItemLevel = 1 << 1,
|
MinimumItemLevel = 1 << 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The silence echo setting.
|
/// The silence echo setting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SilenceEcho = 1 << 2,
|
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