mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 20:33:40 +01:00
StyleCop: everything else
This commit is contained in:
parent
f64c9b8321
commit
595fd3f1e4
134 changed files with 16346 additions and 6202 deletions
|
|
@ -55,9 +55,8 @@ namespace Dalamud.Configuration
|
||||||
File.ReadAllText(path.FullName),
|
File.ReadAllText(path.FullName),
|
||||||
new JsonSerializerSettings
|
new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
TypeNameAssemblyFormatHandling =
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
TypeNameAssemblyFormatHandling.Simple,
|
TypeNameHandling = TypeNameHandling.Objects,
|
||||||
TypeNameHandling = TypeNameHandling.Objects,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,8 +107,8 @@ namespace Dalamud.Configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginName">InternalName of the plugin.</param>
|
/// <param name="pluginName">InternalName of the plugin.</param>
|
||||||
/// <returns>FileInfo of the config file.</returns>
|
/// <returns>FileInfo of the config file.</returns>
|
||||||
public FileInfo GetConfigFile(string pluginName) => new FileInfo(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
|
public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
|
||||||
|
|
||||||
private DirectoryInfo GetDirectoryPath(string pluginName) => new DirectoryInfo(Path.Combine(this.configDirectory.FullName, pluginName));
|
private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Addon;
|
using Dalamud.Game.Addon;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Game.Internal;
|
using Dalamud.Game.Internal;
|
||||||
using Dalamud.Game.Network;
|
using Dalamud.Game.Network;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -182,12 +183,15 @@ namespace Dalamud
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool IsReady { get; private set; }
|
internal bool IsReady { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the plugin system is loaded.
|
||||||
|
/// </summary>
|
||||||
internal bool IsLoadedPluginSystem => this.PluginManager != null;
|
internal bool IsLoadedPluginSystem => this.PluginManager != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets location of stored assets.
|
/// Gets location of stored assets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal DirectoryInfo AssetDirectory => new DirectoryInfo(this.StartInfo.AssetDirectory);
|
internal DirectoryInfo AssetDirectory => new(this.StartInfo.AssetDirectory);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs tier 1 of the Dalamud initialization process.
|
/// Runs tier 1 of the Dalamud initialization process.
|
||||||
|
|
@ -231,7 +235,6 @@ namespace Dalamud
|
||||||
|
|
||||||
Log.Information("[T2] AntiDebug OK!");
|
Log.Information("[T2] AntiDebug OK!");
|
||||||
|
|
||||||
|
|
||||||
this.WinSock2 = new WinSockHandlers();
|
this.WinSock2 = new WinSockHandlers();
|
||||||
|
|
||||||
Log.Information("[T2] WinSock OK!");
|
Log.Information("[T2] WinSock OK!");
|
||||||
|
|
@ -384,7 +387,7 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wait for a queued unload to be finalized.
|
/// Wait for a queued unload to be finalized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void WaitForUnloadFinish()
|
public void WaitForUnloadFinish()
|
||||||
{
|
{
|
||||||
|
|
@ -417,7 +420,7 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose Dalamud subsystems.
|
/// Dispose Dalamud subsystems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
@ -453,12 +456,11 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replace the built-in exception handler with a debug one.
|
/// Replace the built-in exception handler with a debug one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void ReplaceExceptionHandler()
|
internal void ReplaceExceptionHandler()
|
||||||
{
|
{
|
||||||
var releaseFilter = this.SigScanner.ScanText(
|
var releaseFilter = this.SigScanner.ScanText("40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??");
|
||||||
"40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??");
|
|
||||||
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
||||||
|
|
||||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Target">
|
<PropertyGroup Label="Target">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
|
@ -34,6 +34,18 @@
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<NoWarn>IDE0017;IDE0044;IDE0047;IDE0048;IDE1006;CS1573;CS1591;CS1701;CS1702</NoWarn>
|
||||||
|
<!-- IDE0017 - Use object initializers -->
|
||||||
|
<!-- IDE0044 - Add readonly modifier -->
|
||||||
|
<!-- IDE0047 - Parentheses preferences -->
|
||||||
|
<!-- IDE0048 - Parentheses preferences -->
|
||||||
|
<!-- IDE1006 - Naming preferences -->
|
||||||
|
<!-- CS1573 - Parameter has no matching param tag in the XML comment -->
|
||||||
|
<!-- CS1591 - Missing XML comment for publicly visible type or member -->
|
||||||
|
<!-- CS1701 - Runtime policy may be needed -->
|
||||||
|
<!-- CS1702 - Runtime policy may be needed -->
|
||||||
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Resources\Lumina.Generated.dll" />
|
<None Remove="Resources\Lumina.Generated.dll" />
|
||||||
<None Remove="stylecop.json" />
|
<None Remove="stylecop.json" />
|
||||||
|
|
@ -54,7 +66,7 @@
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||||
<PackageReference Include="EasyHook" Version="2.7.6270" />
|
<PackageReference Include="EasyHook" Version="2.7.6270" />
|
||||||
<PackageReference Include="SharpDX.Desktop" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Desktop" Version="4.2.0" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
#pragma warning disable SA1401 // Fields should be private
|
|
||||||
|
|
||||||
namespace Dalamud
|
namespace Dalamud
|
||||||
{
|
{
|
||||||
|
|
@ -50,5 +49,3 @@ namespace Dalamud
|
||||||
public bool OptOutMbCollection;
|
public bool OptOutMbCollection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore SA1401 // Fields should be private
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using Dalamud.Data.LuminaExtensions;
|
using Dalamud.Data.LuminaExtensions;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
@ -22,8 +23,8 @@ namespace Dalamud.Data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataManager : IDisposable
|
public class DataManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly InterfaceManager interfaceManager;
|
|
||||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||||
|
private readonly InterfaceManager interfaceManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="Lumina"/> object which gives access to any excel/game data.
|
/// A <see cref="Lumina"/> object which gives access to any excel/game data.
|
||||||
|
|
@ -36,6 +37,7 @@ namespace Dalamud.Data
|
||||||
/// Initializes a new instance of the <see cref="DataManager"/> class.
|
/// Initializes a new instance of the <see cref="DataManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="language">The language to load data with by default.</param>
|
/// <param name="language">The language to load data with by default.</param>
|
||||||
|
/// <param name="interfaceManager">An <see cref="InterfaceManager"/> instance to parse the data with.</param>
|
||||||
internal DataManager(ClientLanguage language, InterfaceManager interfaceManager)
|
internal DataManager(ClientLanguage language, InterfaceManager interfaceManager)
|
||||||
{
|
{
|
||||||
this.interfaceManager = interfaceManager;
|
this.interfaceManager = interfaceManager;
|
||||||
|
|
@ -71,87 +73,6 @@ namespace Dalamud.Data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDataReady { get; private set; }
|
public bool IsDataReady { get; private set; }
|
||||||
|
|
||||||
/// <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
|
|
||||||
PanicOnSheetChecksumMismatch = true,
|
|
||||||
#else
|
|
||||||
PanicOnSheetChecksumMismatch = false,
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DefaultExcelLanguage = this.Language switch {
|
|
||||||
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
|
||||||
ClientLanguage.English => Lumina.Data.Language.English,
|
|
||||||
ClientLanguage.German => Lumina.Data.Language.German,
|
|
||||||
ClientLanguage.French => Lumina.Data.Language.French,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(
|
|
||||||
nameof(this.Language),
|
|
||||||
@"Unknown Language: " + this.Language),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var processModule = Process.GetCurrentProcess().MainModule;
|
|
||||||
if (processModule != null)
|
|
||||||
{
|
|
||||||
this.gameData =
|
|
||||||
new GameData(
|
|
||||||
Path.Combine(
|
|
||||||
Path.GetDirectoryName(processModule.FileName) !,
|
|
||||||
"sqpack"), luminaOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Information("Lumina is ready: {0}", this.gameData.DataPath);
|
|
||||||
|
|
||||||
this.IsDataReady = true;
|
|
||||||
|
|
||||||
this.luminaResourceThread = new Thread(() =>
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (this.gameData.FileHandleManager.HasPendingFileLoads)
|
|
||||||
{
|
|
||||||
this.gameData.ProcessFileHandleQueue();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Thread.Sleep(5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once FunctionNeverReturns
|
|
||||||
});
|
|
||||||
this.luminaResourceThread.Start();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not download data.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Lumina Wrappers
|
#region Lumina Wrappers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -172,12 +93,13 @@ namespace Dalamud.Data
|
||||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||||
public ExcelSheet<T> GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
public ExcelSheet<T> GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
||||||
{
|
{
|
||||||
var lang = language switch {
|
var lang = language switch
|
||||||
|
{
|
||||||
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
||||||
ClientLanguage.English => Lumina.Data.Language.English,
|
ClientLanguage.English => Lumina.Data.Language.English,
|
||||||
ClientLanguage.German => Lumina.Data.Language.German,
|
ClientLanguage.German => Lumina.Data.Language.German,
|
||||||
ClientLanguage.French => Lumina.Data.Language.French,
|
ClientLanguage.French => Lumina.Data.Language.French,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), @"Unknown Language: " + this.Language)
|
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
|
||||||
};
|
};
|
||||||
return this.Excel.GetSheet<T>(lang);
|
return this.Excel.GetSheet<T>(lang);
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +125,7 @@ namespace Dalamud.Data
|
||||||
var filePath = GameData.ParseFilePath(path);
|
var filePath = GameData.ParseFilePath(path);
|
||||||
if (filePath == null)
|
if (filePath == null)
|
||||||
return default;
|
return default;
|
||||||
return this.gameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile<T>(filePath.Category, filePath) : default(T);
|
return this.gameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile<T>(filePath.Category, filePath) : default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -234,12 +156,13 @@ namespace Dalamud.Data
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||||
public TexFile GetIcon(ClientLanguage iconLanguage, int iconId)
|
public TexFile GetIcon(ClientLanguage iconLanguage, int iconId)
|
||||||
{
|
{
|
||||||
var type = iconLanguage switch {
|
var type = iconLanguage switch
|
||||||
|
{
|
||||||
ClientLanguage.Japanese => "ja/",
|
ClientLanguage.Japanese => "ja/",
|
||||||
ClientLanguage.English => "en/",
|
ClientLanguage.English => "en/",
|
||||||
ClientLanguage.German => "de/",
|
ClientLanguage.German => "de/",
|
||||||
ClientLanguage.French => "fr/",
|
ClientLanguage.French => "fr/",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), @"Unknown Language: " + this.Language)
|
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.GetIcon(type, iconId);
|
return this.GetIcon(type, iconId);
|
||||||
|
|
@ -273,15 +196,16 @@ namespace Dalamud.Data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
||||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||||
public TextureWrap GetImGuiTexture(TexFile tex) =>
|
public TextureWrap GetImGuiTexture(TexFile tex)
|
||||||
this.interfaceManager.LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
=> this.interfaceManager.LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The internal path to the texture.</param>
|
/// <param name="path">The internal path to the texture.</param>
|
||||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
/// <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));
|
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, of the given language.
|
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given language.
|
||||||
|
|
@ -289,8 +213,8 @@ namespace Dalamud.Data
|
||||||
/// <param name="iconLanguage">The requested language.</param>
|
/// <param name="iconLanguage">The requested language.</param>
|
||||||
/// <param name="iconId">The icon ID.</param>
|
/// <param name="iconId">The icon ID.</param>
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||||
public TextureWrap GetImGuiTextureIcon(ClientLanguage iconLanguage, int iconId) =>
|
public TextureWrap GetImGuiTextureIcon(ClientLanguage iconLanguage, int iconId)
|
||||||
this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given type.
|
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given type.
|
||||||
|
|
@ -298,8 +222,8 @@ namespace Dalamud.Data
|
||||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
/// <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>
|
/// <param name="iconId">The icon ID.</param>
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||||
public TextureWrap GetImGuiTextureIcon(string type, int iconId) =>
|
public TextureWrap GetImGuiTextureIcon(string type, int iconId)
|
||||||
this.GetImGuiTexture(this.GetIcon(type, iconId));
|
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
@ -310,5 +234,83 @@ namespace Dalamud.Data
|
||||||
{
|
{
|
||||||
this.luminaResourceThread.Abort();
|
this.luminaResourceThread.Abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
PanicOnSheetChecksumMismatch = true,
|
||||||
|
#else
|
||||||
|
PanicOnSheetChecksumMismatch = false,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DefaultExcelLanguage = this.Language switch
|
||||||
|
{
|
||||||
|
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
||||||
|
ClientLanguage.English => Lumina.Data.Language.English,
|
||||||
|
ClientLanguage.German => Lumina.Data.Language.German,
|
||||||
|
ClientLanguage.French => Lumina.Data.Language.French,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(this.Language),
|
||||||
|
@"Unknown Language: " + this.Language),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var processModule = Process.GetCurrentProcess().MainModule;
|
||||||
|
if (processModule != null)
|
||||||
|
{
|
||||||
|
this.gameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("Lumina is ready: {0}", this.gameData.DataPath);
|
||||||
|
|
||||||
|
this.IsDataReady = true;
|
||||||
|
|
||||||
|
this.luminaResourceThread = new Thread(() =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (this.gameData.FileHandleManager.HasPendingFileLoads)
|
||||||
|
{
|
||||||
|
this.gameData.ProcessFileHandleQueue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Thread.Sleep(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once FunctionNeverReturns
|
||||||
|
});
|
||||||
|
this.luminaResourceThread.Start();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Could not download data.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private (Logger logger, LoggingLevelSwitch levelSwitch) NewLogger(string baseDirectory)
|
private (Logger Logger, LoggingLevelSwitch LevelSwitch) NewLogger(string baseDirectory)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||||
|
|
@ -95,7 +95,7 @@ namespace Dalamud
|
||||||
|
|
||||||
var newLogger = new LoggerConfiguration()
|
var newLogger = new LoggerConfiguration()
|
||||||
.WriteTo.Async(a => a.File(logPath))
|
.WriteTo.Async(a => a.File(logPath))
|
||||||
.WriteTo.EventSink()
|
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||||
.MinimumLevel.ControlledBy(levelSwitch)
|
.MinimumLevel.ControlledBy(levelSwitch)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,24 +10,15 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon
|
namespace Dalamud.Game.Addon
|
||||||
{
|
{
|
||||||
internal unsafe class DalamudSystemMenu
|
/// <summary>
|
||||||
|
/// This class implements in-game Dalamud options in the in-game System menu.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed unsafe partial class DalamudSystemMenu
|
||||||
{
|
{
|
||||||
private readonly Dalamud dalamud;
|
private readonly Dalamud dalamud;
|
||||||
|
|
||||||
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
|
||||||
|
|
||||||
private Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
|
|
||||||
|
|
||||||
private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type);
|
|
||||||
|
|
||||||
private AtkValueChangeType atkValueChangeType;
|
private AtkValueChangeType atkValueChangeType;
|
||||||
|
|
||||||
private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes);
|
|
||||||
|
|
||||||
private AtkValueSetString atkValueSetString;
|
private AtkValueSetString atkValueSetString;
|
||||||
|
private Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
|
||||||
private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
|
|
||||||
|
|
||||||
// TODO: Make this into events in Framework.Gui
|
// TODO: Make this into events in Framework.Gui
|
||||||
private Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
|
private Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
|
||||||
|
|
||||||
|
|
@ -55,14 +46,24 @@ namespace Dalamud.Game.Addon
|
||||||
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
|
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
|
||||||
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetString>(atkValueSetStringAddress);
|
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetString>(atkValueSetStringAddress);
|
||||||
|
|
||||||
var uiModuleRequestMainCommandAddress = this.dalamud.SigScanner.ScanText(
|
var uiModuleRequestMainCommandAddress = this.dalamud.SigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
||||||
"40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
|
||||||
this.hookUiModuleRequestMainCommand = new Hook<UiModuleRequestMainCommand>(
|
this.hookUiModuleRequestMainCommand = new Hook<UiModuleRequestMainCommand>(
|
||||||
uiModuleRequestMainCommandAddress,
|
uiModuleRequestMainCommandAddress,
|
||||||
new UiModuleRequestMainCommand(this.UiModuleRequestMainCommandDetour),
|
new UiModuleRequestMainCommand(this.UiModuleRequestMainCommandDetour),
|
||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||||
|
|
||||||
|
private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type);
|
||||||
|
|
||||||
|
private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes);
|
||||||
|
|
||||||
|
private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the <see cref="DalamudSystemMenu"/>.
|
||||||
|
/// </summary>
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
this.hookAgentHudOpenSystemMenu.Enable();
|
this.hookAgentHudOpenSystemMenu.Enable();
|
||||||
|
|
@ -95,7 +96,7 @@ namespace Dalamud.Game.Addon
|
||||||
this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int
|
this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int
|
||||||
this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int);
|
this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int);
|
||||||
|
|
||||||
for (uint i = menuSize + 2; i > 1; i--)
|
for (var i = menuSize + 2; i > 1; i--)
|
||||||
{
|
{
|
||||||
var curEntry = &atkValueArgs[i + 5 - 2];
|
var curEntry = &atkValueArgs[i + 5 - 2];
|
||||||
var nextEntry = &atkValueArgs[i + 5];
|
var nextEntry = &atkValueArgs[i + 5];
|
||||||
|
|
@ -155,21 +156,44 @@ namespace Dalamud.Game.Addon
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region IDisposable Support
|
/// <summary>
|
||||||
protected virtual void Dispose(bool disposing)
|
/// Implements IDisposable.
|
||||||
{
|
/// </summary>
|
||||||
if (!disposing) return;
|
internal sealed partial class DalamudSystemMenu : IDisposable
|
||||||
|
{
|
||||||
|
private bool disposed = false;
|
||||||
|
|
||||||
this.hookAgentHudOpenSystemMenu.Dispose();
|
/// <summary>
|
||||||
this.hookUiModuleRequestMainCommand.Dispose();
|
/// Finalizes an instance of the <see cref="DalamudSystemMenu"/> class.
|
||||||
}
|
/// </summary>
|
||||||
|
~DalamudSystemMenu() => this.Dispose(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
this.Dispose(true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (this.disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
this.hookAgentHudOpenSystemMenu.Dispose();
|
||||||
|
this.hookUiModuleRequestMainCommand.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,111 +6,156 @@ using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game {
|
namespace Dalamud.Game
|
||||||
public class ChatHandlers {
|
{
|
||||||
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new Dictionary<string, string> {
|
/// <summary>
|
||||||
{"", "<:ffxive071:585847382210642069>"},
|
/// Chat events and public helper functions.
|
||||||
{"", "<:ffxive083:585848592699490329>"}
|
/// </summary>
|
||||||
|
public class ChatHandlers
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||||
|
{
|
||||||
|
{ "", "<:ffxive071:585847382210642069>" },
|
||||||
|
{ "", "<:ffxive083:585848592699490329>" },
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Dalamud dalamud;
|
private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new()
|
||||||
|
{
|
||||||
private DalamudLinkPayload openInstallerWindowLink;
|
{ XivChatType.CrossParty, Color.DodgerBlue },
|
||||||
|
{ XivChatType.Party, Color.DodgerBlue },
|
||||||
private readonly Dictionary<XivChatType, Color> HandledChatTypeColors = new Dictionary<XivChatType, Color> {
|
{ XivChatType.FreeCompany, Color.DeepSkyBlue },
|
||||||
{XivChatType.CrossParty, Color.DodgerBlue},
|
{ XivChatType.CrossLinkShell1, Color.ForestGreen },
|
||||||
{XivChatType.Party, Color.DodgerBlue},
|
{ XivChatType.CrossLinkShell2, Color.ForestGreen },
|
||||||
{XivChatType.FreeCompany, Color.DeepSkyBlue},
|
{ XivChatType.CrossLinkShell3, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell1, Color.ForestGreen},
|
{ XivChatType.CrossLinkShell4, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell2, Color.ForestGreen},
|
{ XivChatType.CrossLinkShell5, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell3, Color.ForestGreen},
|
{ XivChatType.CrossLinkShell6, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell4, Color.ForestGreen},
|
{ XivChatType.CrossLinkShell7, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell5, Color.ForestGreen},
|
{ XivChatType.CrossLinkShell8, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell6, Color.ForestGreen},
|
{ XivChatType.Ls1, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell7, Color.ForestGreen},
|
{ XivChatType.Ls2, Color.ForestGreen },
|
||||||
{XivChatType.CrossLinkShell8, Color.ForestGreen},
|
{ XivChatType.Ls3, Color.ForestGreen },
|
||||||
{XivChatType.Ls1, Color.ForestGreen},
|
{ XivChatType.Ls4, Color.ForestGreen },
|
||||||
{XivChatType.Ls2, Color.ForestGreen},
|
{ XivChatType.Ls5, Color.ForestGreen },
|
||||||
{XivChatType.Ls3, Color.ForestGreen},
|
{ XivChatType.Ls6, Color.ForestGreen },
|
||||||
{XivChatType.Ls4, Color.ForestGreen},
|
{ XivChatType.Ls7, Color.ForestGreen },
|
||||||
{XivChatType.Ls5, Color.ForestGreen},
|
{ XivChatType.Ls8, Color.ForestGreen },
|
||||||
{XivChatType.Ls6, Color.ForestGreen},
|
{ XivChatType.TellIncoming, Color.HotPink },
|
||||||
{XivChatType.Ls7, Color.ForestGreen},
|
{ XivChatType.PvPTeam, Color.SandyBrown },
|
||||||
{XivChatType.Ls8, Color.ForestGreen},
|
{ XivChatType.Urgent, Color.DarkViolet },
|
||||||
{XivChatType.TellIncoming, Color.HotPink},
|
{ XivChatType.NoviceNetwork, Color.SaddleBrown },
|
||||||
{XivChatType.PvPTeam, Color.SandyBrown},
|
{ XivChatType.Echo, Color.Gray },
|
||||||
{XivChatType.Urgent, Color.DarkViolet},
|
|
||||||
{XivChatType.NoviceNetwork, Color.SaddleBrown},
|
|
||||||
{XivChatType.Echo, Color.Gray}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Regex rmtRegex =
|
private readonly Regex rmtRegex = new(
|
||||||
new Regex(
|
|
||||||
@"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",
|
@"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);
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new Dictionary<ClientLanguage, Regex[]>() { {
|
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[] {
|
|
||||||
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.English,
|
||||||
|
new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ClientLanguage.German,
|
||||||
|
new Regex[]
|
||||||
|
{
|
||||||
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[] {
|
{
|
||||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled)
|
ClientLanguage.French,
|
||||||
|
new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Regex urlRegex =
|
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||||
new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?",
|
|
||||||
RegexOptions.Compiled);
|
|
||||||
|
|
||||||
|
private readonly Dalamud dalamud;
|
||||||
|
private DalamudLinkPayload openInstallerWindowLink;
|
||||||
private bool hasSeenLoadingMsg;
|
private bool hasSeenLoadingMsg;
|
||||||
|
|
||||||
public string LastLink { get; private set; }
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
|
||||||
public ChatHandlers(Dalamud dalamud) {
|
/// </summary>
|
||||||
|
/// <param name="dalamud">Dalamud instance.</param>
|
||||||
|
public ChatHandlers(Dalamud dalamud)
|
||||||
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
|
|
||||||
dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
|
|
||||||
|
|
||||||
this.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) => {
|
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += this.OnCheckMessageHandled;
|
||||||
|
dalamud.Framework.Gui.Chat.OnChatMessage += this.OnChatMessage;
|
||||||
|
|
||||||
|
this.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||||
|
{
|
||||||
this.dalamud.DalamudUi.OpenPluginInstaller();
|
this.dalamud.DalamudUi.OpenPluginInstaller();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) {
|
/// <summary>
|
||||||
|
/// Gets the last URL seen in chat.
|
||||||
|
/// </summary>
|
||||||
|
public string LastLink { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a string to SeString and wrap in italics payloads.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to convert.</param>
|
||||||
|
/// <returns>SeString payload of italicized text.</returns>
|
||||||
|
private static SeString MakeItalics(string text)
|
||||||
|
{
|
||||||
|
// TODO: when the code OnCharMessage is switched to SeString, this can be a straight insertion of the
|
||||||
|
// italics payloads only, and be a lot cleaner
|
||||||
|
var italicString = new SeString(new List<Payload>(new Payload[]
|
||||||
|
{
|
||||||
|
EmphasisItalicPayload.ItalicsOn,
|
||||||
|
new TextPayload(text),
|
||||||
|
EmphasisItalicPayload.ItalicsOff,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return italicString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
|
{
|
||||||
var textVal = message.TextValue;
|
var textVal = message.TextValue;
|
||||||
|
|
||||||
var matched = this.rmtRegex.IsMatch(textVal);
|
var matched = this.rmtRegex.IsMatch(textVal);
|
||||||
if (matched) {
|
if (matched)
|
||||||
|
{
|
||||||
// This seems to be a RMT ad - let's not show it
|
// This seems to be a RMT ad - let's not show it
|
||||||
Log.Debug("Handled RMT ad: " + message.TextValue);
|
Log.Debug("Handled RMT ad: " + message.TextValue);
|
||||||
isHandled = true;
|
isHandled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.dalamud.Configuration.BadWords != null &&
|
if (this.dalamud.Configuration.BadWords != null &&
|
||||||
this.dalamud.Configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) {
|
this.dalamud.Configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||||
|
{
|
||||||
// This seems to be in the user block list - let's not show it
|
// This seems to be in the user block list - let's not show it
|
||||||
Log.Debug("Blocklist triggered");
|
Log.Debug("Blocklist triggered");
|
||||||
isHandled = true;
|
isHandled = true;
|
||||||
|
|
@ -118,23 +163,24 @@ namespace Dalamud.Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender,
|
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
ref SeString message, ref bool isHandled) {
|
{
|
||||||
|
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
this.PrintWelcomeMessage();
|
||||||
PrintWelcomeMessage();
|
|
||||||
|
|
||||||
// For injections while logged in
|
// For injections while logged in
|
||||||
if (this.dalamud.ClientState.LocalPlayer != null && this.dalamud.ClientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
if (this.dalamud.ClientState.LocalPlayer != null && this.dalamud.ClientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||||
PrintWelcomeMessage();
|
this.PrintWelcomeMessage();
|
||||||
|
|
||||||
#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 retainerSaleRegexes[dalamud.StartInfo.Language]) {
|
{
|
||||||
|
foreach (var regex in this.retainerSaleRegexes[this.dalamud.StartInfo.Language])
|
||||||
|
{
|
||||||
var matchInfo = regex.Match(message.TextValue);
|
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
|
// we no longer really need to do/validate the item matching since we read the id from the byte array
|
||||||
|
|
@ -143,10 +189,9 @@ namespace Dalamud.Game {
|
||||||
if (!itemInfo.Success)
|
if (!itemInfo.Success)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var itemLink =
|
var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
|
||||||
message.Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload;
|
if (itemLink == default)
|
||||||
|
{
|
||||||
if (itemLink == null) {
|
|
||||||
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
|
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -155,10 +200,10 @@ namespace Dalamud.Game {
|
||||||
|
|
||||||
var valueInfo = matchInfo.Groups["value"];
|
var valueInfo = matchInfo.Groups["value"];
|
||||||
// not sure if using a culture here would work correctly, so just strip symbols instead
|
// not sure if using a culture here would work correctly, so just strip symbols instead
|
||||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue))
|
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
//Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
// Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +213,7 @@ namespace Dalamud.Game {
|
||||||
|
|
||||||
var linkMatch = this.urlRegex.Match(message.TextValue);
|
var linkMatch = this.urlRegex.Match(message.TextValue);
|
||||||
if (linkMatch.Value.Length > 0)
|
if (linkMatch.Value.Length > 0)
|
||||||
LastLink = linkMatch.Value;
|
this.LastLink = linkMatch.Value;
|
||||||
|
|
||||||
// Handle all of this with SeString some day
|
// Handle all of this with SeString some day
|
||||||
/*
|
/*
|
||||||
|
|
@ -193,43 +238,60 @@ namespace Dalamud.Game {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintWelcomeMessage() {
|
private void PrintWelcomeMessage()
|
||||||
|
{
|
||||||
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
||||||
|
|
||||||
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
|
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
|
||||||
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), this.dalamud.PluginManager.Plugins.Count));
|
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), this.dalamud.PluginManager.Plugins.Count));
|
||||||
|
|
||||||
if (this.dalamud.Configuration.PrintPluginsWelcomeMsg) {
|
if (this.dalamud.Configuration.PrintPluginsWelcomeMsg)
|
||||||
foreach (var plugin in this.dalamud.PluginManager.Plugins.OrderBy(x => x.Plugin.Name)) {
|
{
|
||||||
|
foreach (var plugin in this.dalamud.PluginManager.Plugins.OrderBy(x => x.Plugin.Name))
|
||||||
|
{
|
||||||
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Plugin.Name, plugin.Definition.AssemblyVersion));
|
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Plugin.Name, plugin.Definition.AssemblyVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion)) {
|
if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion))
|
||||||
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
{
|
||||||
|
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry
|
||||||
|
{
|
||||||
MessageBytes = Encoding.UTF8.GetBytes(Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog.")),
|
MessageBytes = Encoding.UTF8.GetBytes(Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog.")),
|
||||||
Type = XivChatType.Notice
|
Type = XivChatType.Notice,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (DalamudChangelogWindow.WarrantsChangelog)
|
if (DalamudChangelogWindow.WarrantsChangelog)
|
||||||
|
#pragma warning disable CS0162 // Unreachable code detected
|
||||||
this.dalamud.DalamudUi.OpenChangelog();
|
this.dalamud.DalamudUi.OpenChangelog();
|
||||||
|
#pragma warning restore CS0162 // Unreachable code detected
|
||||||
|
|
||||||
this.dalamud.Configuration.LastVersion = assemblyVersion;
|
this.dalamud.Configuration.LastVersion = assemblyVersion;
|
||||||
this.dalamud.Configuration.Save();
|
this.dalamud.Configuration.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(() => this.dalamud.PluginRepository.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins)).ContinueWith(t => {
|
Task.Run(() => this.dalamud.PluginRepository.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins)).ContinueWith(t =>
|
||||||
if (t.IsFaulted) {
|
{
|
||||||
|
if (t.IsFaulted)
|
||||||
|
{
|
||||||
Log.Error(t.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
Log.Error(t.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
var updatedPlugins = t.Result.UpdatedPlugins;
|
var updatedPlugins = t.Result.UpdatedPlugins;
|
||||||
|
|
||||||
if (updatedPlugins != null && updatedPlugins.Any()) {
|
if (updatedPlugins != null && updatedPlugins.Any())
|
||||||
if (this.dalamud.Configuration.AutoUpdatePlugins) {
|
{
|
||||||
|
if (this.dalamud.Configuration.AutoUpdatePlugins)
|
||||||
|
{
|
||||||
this.dalamud.PluginRepository.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
|
this.dalamud.PluginRepository.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
|
||||||
} else {
|
}
|
||||||
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
else
|
||||||
MessageBytes = new SeString(new List<Payload>() {
|
{
|
||||||
|
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry
|
||||||
|
{
|
||||||
|
MessageBytes = 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(this.dalamud.Data, 500),
|
new UIForegroundPayload(this.dalamud.Data, 500),
|
||||||
|
|
@ -239,7 +301,7 @@ namespace Dalamud.Game {
|
||||||
new UIForegroundPayload(this.dalamud.Data, 0),
|
new UIForegroundPayload(this.dalamud.Data, 0),
|
||||||
new TextPayload("]"),
|
new TextPayload("]"),
|
||||||
}).Encode(),
|
}).Encode(),
|
||||||
Type = XivChatType.Urgent
|
Type = XivChatType.Urgent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -248,17 +310,5 @@ namespace Dalamud.Game {
|
||||||
|
|
||||||
this.hasSeenLoadingMsg = true;
|
this.hasSeenLoadingMsg = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SeString MakeItalics(string text) {
|
|
||||||
// TODO: when the above code is switched to SeString, this can be a straight insertion of the
|
|
||||||
// italics payloads only, and be a lot cleaner
|
|
||||||
var italicString = new SeString(new List<Payload>(new Payload[] {
|
|
||||||
EmphasisItalicPayload.ItalicsOn,
|
|
||||||
new TextPayload(text),
|
|
||||||
EmphasisItalicPayload.ItalicsOff
|
|
||||||
}));
|
|
||||||
|
|
||||||
return italicString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,170 +1,224 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors {
|
namespace Dalamud.Game.ClientState.Actors
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the currently spawned FFXIV actors.
|
/// This collection represents the currently spawned FFXIV actors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable {
|
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable
|
||||||
|
{
|
||||||
private const int ActorTableLength = 424;
|
private const int ActorTableLength = 424;
|
||||||
|
|
||||||
#region Actor Table Cache
|
#region ReadProcessMemory Hack
|
||||||
|
private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
|
||||||
|
private static readonly IntPtr ActorMem = Marshal.AllocHGlobal(ActorMemSize);
|
||||||
|
private static readonly IntPtr CurrentProcessHandle = new(-1);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private Dalamud dalamud;
|
||||||
|
private ClientStateAddressResolver address;
|
||||||
private List<Actor> actorsCache;
|
private List<Actor> actorsCache;
|
||||||
|
|
||||||
private List<Actor> ActorsCache {
|
|
||||||
get {
|
|
||||||
if (this.actorsCache != null) return this.actorsCache;
|
|
||||||
this.actorsCache = GetActorTable();
|
|
||||||
return this.actorsCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetCache() => actorsCache = null;
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region ReadProcessMemory Hack
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
|
||||||
static extern bool ReadProcessMemory(
|
|
||||||
IntPtr hProcess,
|
|
||||||
IntPtr lpBaseAddress,
|
|
||||||
IntPtr lpBuffer,
|
|
||||||
int dwSize,
|
|
||||||
out IntPtr lpNumberOfBytesRead);
|
|
||||||
|
|
||||||
private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
|
|
||||||
private IntPtr actorMem = Marshal.AllocHGlobal(ActorMemSize);
|
|
||||||
private IntPtr currentProcessHandle = new IntPtr(-1);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private ClientStateAddressResolver Address { get; }
|
|
||||||
private Dalamud dalamud;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set up the actor table collection.
|
/// Initializes a new instance of the <see cref="ActorTable"/> class.
|
||||||
|
/// Set up the actor table collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressResolver">Client state address resolver.</param>
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
|
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||||
Address = addressResolver;
|
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
||||||
|
{
|
||||||
|
this.address = addressResolver;
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
dalamud.Framework.OnUpdateEvent += Framework_OnUpdateEvent;
|
dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent;
|
||||||
|
|
||||||
Log.Verbose("Actor table address {ActorTable}", Address.ActorTable);
|
Log.Verbose("Actor table address {ActorTable}", this.address.ActorTable);
|
||||||
}
|
|
||||||
|
|
||||||
private void Framework_OnUpdateEvent(Internal.Framework framework) {
|
|
||||||
this.ResetCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get an actor at the specified spawn index.
|
/// Gets the amount of currently spawned actors.
|
||||||
|
/// </summary>
|
||||||
|
public int Length => this.ActorsCache.Count;
|
||||||
|
|
||||||
|
private List<Actor> ActorsCache => this.actorsCache ??= this.GetActorTable();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an actor at the specified spawn index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Spawn index.</param>
|
/// <param name="index">Spawn index.</param>
|
||||||
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
|
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public Actor this[int index] {
|
public Actor this[int index] => this.ActorsCache[index];
|
||||||
get => ActorsCache[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read an actor struct from memory and create the appropriate <see cref="ObjectKind"/> type of actor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset of the actor in the actor table.</param>
|
||||||
|
/// <returns>An instantiated actor.</returns>
|
||||||
internal Actor ReadActorFromMemory(IntPtr offset)
|
internal Actor ReadActorFromMemory(IntPtr offset)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
// FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
|
// FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
|
||||||
if (!ReadProcessMemory(this.currentProcessHandle, offset, this.actorMem, ActorMemSize, out _))
|
if (!NativeFunctions.ReadProcessMemory(CurrentProcessHandle, offset, ActorMem, ActorMemSize, out _))
|
||||||
{
|
{
|
||||||
Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
|
Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(this.actorMem);
|
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(ActorMem);
|
||||||
|
|
||||||
return actorStruct.ObjectKind switch {
|
return actorStruct.ObjectKind switch
|
||||||
|
{
|
||||||
ObjectKind.Player => new PlayerCharacter(offset, actorStruct, this.dalamud),
|
ObjectKind.Player => new PlayerCharacter(offset, actorStruct, this.dalamud),
|
||||||
ObjectKind.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud),
|
ObjectKind.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud),
|
||||||
ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud),
|
ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud),
|
||||||
ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud),
|
ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud),
|
||||||
_ => new Actor(offset, actorStruct, this.dalamud)
|
_ => new Actor(offset, actorStruct, this.dalamud),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e)
|
||||||
|
{
|
||||||
Log.Error(e, "Could not read actor from memory.");
|
Log.Error(e, "Could not read actor from memory.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr[] GetPointerTable() {
|
private void ResetCache() => this.actorsCache = null;
|
||||||
|
|
||||||
|
private void Framework_OnUpdateEvent(Internal.Framework framework)
|
||||||
|
{
|
||||||
|
this.ResetCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr[] GetPointerTable()
|
||||||
|
{
|
||||||
var ret = new IntPtr[ActorTableLength];
|
var ret = new IntPtr[ActorTableLength];
|
||||||
Marshal.Copy(Address.ActorTable, ret, 0, ActorTableLength);
|
Marshal.Copy(this.address.ActorTable, ret, 0, ActorTableLength);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Actor> GetActorTable() {
|
private List<Actor> GetActorTable()
|
||||||
|
{
|
||||||
var actors = new List<Actor>();
|
var actors = new List<Actor>();
|
||||||
var ptrTable = GetPointerTable();
|
var ptrTable = this.GetPointerTable();
|
||||||
for (var i = 0; i < ActorTableLength; i++) {
|
for (var i = 0; i < ActorTableLength; i++)
|
||||||
actors.Add(ptrTable[i] != IntPtr.Zero ? ReadActorFromMemory(ptrTable[i]) : null);
|
{
|
||||||
|
actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actors;
|
return actors;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<Actor> GetEnumerator() {
|
/// <summary>
|
||||||
return ActorsCache.Where(a => a != null).GetEnumerator();
|
/// Implementing IDisposable.
|
||||||
}
|
/// </summary>
|
||||||
|
public sealed partial class ActorTable : IDisposable
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
{
|
||||||
return GetEnumerator();
|
private bool disposed = false;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of currently spawned actors.
|
/// Finalizes an instance of the <see cref="ActorTable"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Length => ActorsCache.Count;
|
~ActorTable() => this.Dispose(false);
|
||||||
|
|
||||||
int IReadOnlyCollection<Actor>.Count => Length;
|
/// <summary>
|
||||||
|
/// Disposes of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
int ICollection.Count => Length;
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (this.disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
this.dalamud.Framework.OnUpdateEvent -= this.Framework_OnUpdateEvent;
|
||||||
|
Marshal.FreeHGlobal(ActorMem);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementing IReadOnlyCollection, IEnumerable, and Enumerable.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ActorTable : IReadOnlyCollection<Actor>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of elements in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The number of elements in the collection.</returns>
|
||||||
|
int IReadOnlyCollection<Actor>.Count => this.Length;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an enumerator capable of iterating through the actor table.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An actor enumerable.</returns>
|
||||||
|
public IEnumerator<Actor> GetEnumerator() => this.ActorsCache.Where(a => a != null).GetEnumerator();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an enumerator capable of iterating through the actor table.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An actor enumerable.</returns>
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementing ICollection.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ActorTable : ICollection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of elements in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The number of elements in the collection.</returns>
|
||||||
|
int ICollection.Count => this.Length;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether access to the collection is synchronized (thread safe).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether access is synchronized (thread safe) or not.</returns>
|
||||||
bool ICollection.IsSynchronized => false;
|
bool ICollection.IsSynchronized => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an object that can be used to synchronize access to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An object that can be used to synchronize access to the collection.</returns>
|
||||||
object ICollection.SyncRoot => this;
|
object ICollection.SyncRoot => this;
|
||||||
|
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
/// <summary>
|
||||||
for (var i = 0; i < Length; i++) {
|
/// Copies the elements of the collection to an array, starting at a particular index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">
|
||||||
|
/// The one-dimensional array that is the destination of the elements copied from the collection. The array must have zero-based indexing.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="index">
|
||||||
|
/// The zero-based index in array at which copying begins.
|
||||||
|
/// </param>
|
||||||
|
void ICollection.CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
array.SetValue(this[i], index);
|
array.SetValue(this[i], index);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable Pattern
|
|
||||||
private bool disposed = false;
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (this.disposed) return;
|
|
||||||
this.dalamud.Framework.OnUpdateEvent -= Framework_OnUpdateEvent;
|
|
||||||
Marshal.FreeHGlobal(this.actorMem);
|
|
||||||
this.disposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
~ActorTable() {
|
|
||||||
Dispose(false);
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,11 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors
|
namespace Dalamud.Game.ClientState.Actors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This enum describes the indices of the Customize array.
|
/// This enum describes the indices of the Customize array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
|
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
|
||||||
public enum CustomizeIndex {
|
public enum CustomizeIndex
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The race of the character.
|
/// The race of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -35,12 +30,12 @@ namespace Dalamud.Game.ClientState.Actors
|
||||||
/// 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>
|
||||||
|
|
@ -50,12 +45,12 @@ namespace Dalamud.Game.ClientState.Actors
|
||||||
/// 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>
|
||||||
|
|
@ -125,17 +120,17 @@ namespace Dalamud.Game.ClientState.Actors
|
||||||
/// 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>
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,83 @@
|
||||||
namespace Dalamud.Game.ClientState.Actors {
|
namespace Dalamud.Game.ClientState.Actors
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum describing possible entity kinds.
|
/// Enum describing possible entity kinds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ObjectKind : byte {
|
public enum ObjectKind : byte
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid actor.
|
/// Invalid actor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0x00,
|
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>
|
||||||
|
/// Objects representing area objects.
|
||||||
|
/// </summary>
|
||||||
Area = 0x0B,
|
Area = 0x0B,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing housing objects.
|
/// Objects representing housing objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Housing = 0x0C,
|
Housing = 0x0C,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objects representing cutscene objects.
|
||||||
|
/// </summary>
|
||||||
Cutscene = 0x0D,
|
Cutscene = 0x0D,
|
||||||
CardStand = 0x0E
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objects representing card stand objects.
|
||||||
|
/// </summary>
|
||||||
|
CardStand = 0x0E,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,38 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors {
|
namespace Dalamud.Game.ClientState.Actors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A game native equivalent of a Vector3.
|
||||||
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct Position3 {
|
public struct Position3
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The X of (X,Z,Y).
|
||||||
|
/// </summary>
|
||||||
public float X;
|
public float X;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Z of (X,Z,Y).
|
||||||
|
/// </summary>
|
||||||
public float Z;
|
public float Z;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Y of (X,Z,Y).
|
||||||
|
/// </summary>
|
||||||
public float Y;
|
public float Y;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert this Position3 to a System.Numerics.Vector3
|
/// Convert this Position3 to a System.Numerics.Vector3.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pos">Position to convert.</param>
|
/// <param name="pos">Position to convert.</param>
|
||||||
public static implicit operator System.Numerics.Vector3(Position3 pos) => new System.Numerics.Vector3(pos.X, pos.Y, pos.Z);
|
public static implicit operator System.Numerics.Vector3(Position3 pos) => new(pos.X, pos.Y, pos.Z);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert this Position3 to a SharpDX.Vector3
|
/// Convert this Position3 to a SharpDX.Vector3.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pos">Position to convert.</param>
|
/// <param name="pos">Position to convert.</param>
|
||||||
public static implicit operator SharpDX.Vector3(Position3 pos) => new SharpDX.Vector3(pos.X, pos.Z, pos.Y);
|
public static implicit operator SharpDX.Vector3(Position3 pos) => new(pos.X, pos.Z, pos.Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,24 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
||||||
{
|
{
|
||||||
public abstract class BaseResolver {
|
/// <summary>
|
||||||
protected Dalamud dalamud;
|
/// Base object resolver.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseResolver
|
||||||
|
{
|
||||||
|
private Dalamud dalamud;
|
||||||
|
|
||||||
public BaseResolver(Dalamud dalamud) {
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseResolver"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public BaseResolver(Dalamud dalamud)
|
||||||
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Dalamud instance.
|
||||||
|
/// </summary>
|
||||||
|
protected Dalamud Dalamud => this.dalamud;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,31 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This object represents a class or job.
|
/// This object represents a class or job.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ClassJob : BaseResolver {
|
public class ClassJob : BaseResolver
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ID of the ClassJob.
|
/// ID of the ClassJob.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly uint Id;
|
public readonly uint Id;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GameData linked to this ClassJob.
|
/// Initializes a new instance of the <see cref="ClassJob"/> class.
|
||||||
/// </summary>
|
|
||||||
public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
|
|
||||||
this.dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.ClassJob>().GetRow(this.Id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set up the ClassJob resolver with the provided ID.
|
/// Set up the ClassJob resolver with the provided ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ID of the world.</param>
|
/// <param name="id">The ID of the classJob.</param>
|
||||||
public ClassJob(byte id, Dalamud dalamud) : base(dalamud) {
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public ClassJob(byte id, Dalamud dalamud)
|
||||||
|
: base(dalamud)
|
||||||
|
{
|
||||||
this.Id = id;
|
this.Id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets GameData linked to this ClassJob.
|
||||||
|
/// </summary>
|
||||||
|
public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
|
||||||
|
this.Dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.ClassJob>().GetRow(this.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,31 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This object represents a world a character can reside on.
|
/// This object represents a world a character can reside on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class World : BaseResolver {
|
public class World : BaseResolver
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ID of the world.
|
/// ID of the world.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly uint Id;
|
public readonly uint Id;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GameData linked to this world.
|
/// Initializes a new instance of the <see cref="World"/> class.
|
||||||
/// </summary>
|
|
||||||
public Lumina.Excel.GeneratedSheets.World GameData =>
|
|
||||||
this.dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.World>().GetRow(this.Id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set up the world resolver with the provided ID.
|
/// Set up the world resolver with the provided ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ID of the world.</param>
|
/// <param name="id">The ID of the world.</param>
|
||||||
public World(ushort id, Dalamud dalamud) : base(dalamud) {
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public World(ushort id, Dalamud dalamud)
|
||||||
|
: base(dalamud)
|
||||||
|
{
|
||||||
this.Id = id;
|
this.Id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets GameData linked to this world.
|
||||||
|
/// </summary>
|
||||||
|
public Lumina.Excel.GeneratedSheets.World GameData =>
|
||||||
|
this.Dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.World>().GetRow(this.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,118 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors {
|
namespace Dalamud.Game.ClientState.Actors
|
||||||
public static class TargetOffsets {
|
{
|
||||||
public const int CurrentTarget = 0x80;
|
/// <summary>
|
||||||
public const int MouseOverTarget = 0xD0;
|
/// Get and set various kinds of targets for the player.
|
||||||
public const int FocusTarget = 0xF8;
|
/// </summary>
|
||||||
public const int PreviousTarget = 0x110;
|
public sealed class Targets
|
||||||
public const int SoftTarget = 0x88;
|
{
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class Targets {
|
|
||||||
private ClientStateAddressResolver Address { get; }
|
|
||||||
private Dalamud dalamud;
|
private Dalamud dalamud;
|
||||||
|
private ClientStateAddressResolver address;
|
||||||
|
|
||||||
public Actor CurrentTarget => GetActorByOffset(TargetOffsets.CurrentTarget);
|
/// <summary>
|
||||||
public Actor MouseOverTarget => GetActorByOffset(TargetOffsets.MouseOverTarget);
|
/// Initializes a new instance of the <see cref="Targets"/> class.
|
||||||
public Actor FocusTarget => GetActorByOffset(TargetOffsets.FocusTarget);
|
/// </summary>
|
||||||
public Actor PreviousTarget => GetActorByOffset(TargetOffsets.PreviousTarget);
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
public Actor SoftTarget => GetActorByOffset(TargetOffsets.SoftTarget);
|
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||||
|
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
||||||
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
Address = addressResolver;
|
this.address = addressResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCurrentTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
|
/// <summary>
|
||||||
public void SetCurrentTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.CurrentTarget);
|
/// Gets the current target.
|
||||||
|
/// </summary>
|
||||||
|
public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
|
||||||
|
|
||||||
public void SetFocusTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
|
/// <summary>
|
||||||
public void SetFocusTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.FocusTarget);
|
/// Gets the mouseover target.
|
||||||
|
/// </summary>
|
||||||
|
public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
|
||||||
|
|
||||||
public void ClearCurrentTarget() => SetCurrentTarget(IntPtr.Zero);
|
/// <summary>
|
||||||
public void ClearFocusTarget() => SetFocusTarget(IntPtr.Zero);
|
/// Gets the focus target.
|
||||||
|
/// </summary>
|
||||||
|
public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
|
||||||
|
|
||||||
private void SetTarget(IntPtr actorAddress, int offset) {
|
/// <summary>
|
||||||
if (Address.TargetManager == IntPtr.Zero) return;
|
/// Gets the previous target.
|
||||||
Marshal.WriteIntPtr(Address.TargetManager, offset, actorAddress);
|
/// </summary>
|
||||||
|
public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the soft target.
|
||||||
|
/// </summary>
|
||||||
|
public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the current target.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor">Actor to target.</param>
|
||||||
|
public void SetCurrentTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the current target.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actorAddress">Actor (address) to target.</param>
|
||||||
|
public void SetCurrentTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.CurrentTarget);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the focus target.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor">Actor to focus.</param>
|
||||||
|
public void SetFocusTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the focus target.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actorAddress">Actor (address) to focus.</param>
|
||||||
|
public void SetFocusTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.FocusTarget);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the current target.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearCurrentTarget() => this.SetCurrentTarget(IntPtr.Zero);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the focus target.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
|
||||||
|
|
||||||
|
private void SetTarget(IntPtr actorAddress, int offset)
|
||||||
|
{
|
||||||
|
if (this.address.TargetManager == IntPtr.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Actor GetActorByOffset(int offset) {
|
private Actor GetActorByOffset(int offset)
|
||||||
if (Address.TargetManager == IntPtr.Zero) return null;
|
{
|
||||||
var actorAddress = Marshal.ReadIntPtr(Address.TargetManager + offset);
|
if (this.address.TargetManager == IntPtr.Zero)
|
||||||
if (actorAddress == IntPtr.Zero) return null;
|
return null;
|
||||||
|
|
||||||
|
var actorAddress = Marshal.ReadIntPtr(this.address.TargetManager + offset);
|
||||||
|
if (actorAddress == IntPtr.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress);
|
return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Memory offsets for the <see cref="Targets"/> type.
|
||||||
|
/// </summary>
|
||||||
|
public static class TargetOffsets
|
||||||
|
{
|
||||||
|
public const int CurrentTarget = 0x80;
|
||||||
|
public const int SoftTarget = 0x88;
|
||||||
|
public const int MouseOverTarget = 0xD0;
|
||||||
|
public const int FocusTarget = 0xF8;
|
||||||
|
public const int PreviousTarget = 0x110;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,11 @@ using Dalamud.Game.ClientState.Structs;
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types
|
namespace Dalamud.Game.ClientState.Actors.Types
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a basic FFXIV actor.
|
/// This class represents a basic FFXIV actor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Actor : IEquatable<Actor>
|
public class Actor : IEquatable<Actor>
|
||||||
{
|
{
|
||||||
private readonly Structs.Actor actorStruct;
|
private readonly Structs.Actor actorStruct;
|
||||||
// This is a breaking change. StyleCop demands it.
|
|
||||||
// private readonly IntPtr address;
|
|
||||||
private readonly Dalamud dalamud;
|
private readonly Dalamud dalamud;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -83,8 +81,6 @@ namespace Dalamud.Game.ClientState.Actors.Types
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of this actor in memory.
|
/// Gets the address of this actor in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// TODO: This is a breaking change, StyleCop demands it.
|
|
||||||
// public IntPtr Address => this.address;
|
|
||||||
public readonly IntPtr Address;
|
public readonly IntPtr Address;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ namespace Dalamud.Game.ClientState.Actors.Types
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ClassJob of this Chara.
|
/// Gets the ClassJob of this Chara.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClassJob ClassJob => new ClassJob(this.ActorStruct.ClassJob, this.Dalamud);
|
public ClassJob ClassJob => new(this.ActorStruct.ClassJob, this.Dalamud);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current HP of this Chara.
|
/// Gets the current HP of this Chara.
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,51 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types
|
namespace Dalamud.Game.ClientState.Actors.Types
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a party member.
|
||||||
|
/// </summary>
|
||||||
public class PartyMember
|
public class PartyMember
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the character.
|
||||||
|
/// </summary>
|
||||||
public string CharacterName;
|
public string CharacterName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unknown.
|
||||||
|
/// </summary>
|
||||||
public long Unknown;
|
public long Unknown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actor object that corresponds to this party member.
|
||||||
|
/// </summary>
|
||||||
public Actor Actor;
|
public Actor Actor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The kind or type of actor.
|
||||||
|
/// </summary>
|
||||||
public ObjectKind ObjectKind;
|
public ObjectKind ObjectKind;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="table">The ActorTable instance.</param>
|
||||||
|
/// <param name="rawData">The interop data struct.</param>
|
||||||
public PartyMember(ActorTable table, Structs.PartyMember rawData)
|
public PartyMember(ActorTable table, Structs.PartyMember rawData)
|
||||||
{
|
{
|
||||||
CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
|
this.CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
|
||||||
Unknown = rawData.unknown;
|
this.Unknown = rawData.unknown;
|
||||||
Actor = null;
|
this.Actor = null;
|
||||||
for (var i = 0; i < table.Length; i++)
|
for (var i = 0; i < table.Length; i++)
|
||||||
{
|
{
|
||||||
if (table[i] != null && table[i].ActorId == rawData.actorId)
|
if (table[i] != null && table[i].ActorId == rawData.actorId)
|
||||||
{
|
{
|
||||||
Actor = table[i];
|
this.Actor = table[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObjectKind = rawData.objectKind;
|
|
||||||
|
this.ObjectKind = rawData.objectKind;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,12 @@ namespace Dalamud.Game.ClientState.Actors.Types
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current <see cref="World">world</see> of the character.
|
/// Gets the current <see cref="World">world</see> of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public World CurrentWorld => new World(this.ActorStruct.CurrentWorld, this.Dalamud);
|
public World CurrentWorld => new(this.ActorStruct.CurrentWorld, this.Dalamud);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the home <see cref="World">world</see> of the character.
|
/// Gets the home <see cref="World">world</see> of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public World HomeWorld => new World(this.ActorStruct.HomeWorld, this.Dalamud);
|
public World HomeWorld => new(this.ActorStruct.HomeWorld, this.Dalamud);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Free Company tag of this player.
|
/// Gets the Free Company tag of this player.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Actors;
|
using Dalamud.Game.ClientState.Actors;
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
using Dalamud.Game.Internal;
|
using Dalamud.Game.Internal;
|
||||||
using Dalamud.Game.Internal.Network;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
@ -15,41 +15,17 @@ namespace Dalamud.Game.ClientState
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents the state of the game client at the time of access.
|
/// This class represents the state of the game client at the time of access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ClientState : INotifyPropertyChanged, IDisposable {
|
public class ClientState : INotifyPropertyChanged, IDisposable
|
||||||
private readonly Dalamud dalamud;
|
{
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
private ClientStateAddressResolver Address { get; }
|
|
||||||
|
|
||||||
public readonly ClientLanguage ClientLanguage;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The table of all present actors.
|
/// The table of all present actors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ActorTable Actors;
|
public readonly ActorTable Actors;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The local player character, if one is present.
|
/// Gets the language of the client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CanBeNull]
|
public readonly ClientLanguage ClientLanguage;
|
||||||
public PlayerCharacter LocalPlayer {
|
|
||||||
get {
|
|
||||||
var actor = this.Actors[0];
|
|
||||||
|
|
||||||
if (actor is PlayerCharacter pc)
|
|
||||||
return pc;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region TerritoryType
|
|
||||||
|
|
||||||
// TODO: The hooking logic for this should go into a separate class.
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
|
||||||
|
|
||||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current Territory the player resides in.
|
/// The current Territory the player resides in.
|
||||||
|
|
@ -57,39 +33,12 @@ namespace Dalamud.Game.ClientState
|
||||||
public ushort TerritoryType;
|
public ushort TerritoryType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that gets fired when the current Territory changes.
|
/// The class facilitating Job Gauge data access.
|
||||||
/// </summary>
|
|
||||||
public EventHandler<ushort> TerritoryChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that gets fired when a duty is ready.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ContentFinderCondition> CfPop;
|
|
||||||
|
|
||||||
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
|
||||||
{
|
|
||||||
this.TerritoryType = terriType;
|
|
||||||
this.TerritoryChanged?.Invoke(this, terriType);
|
|
||||||
|
|
||||||
Log.Debug("TerritoryType changed: {0}", terriType);
|
|
||||||
|
|
||||||
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The content ID of the local character.
|
|
||||||
/// </summary>
|
|
||||||
public ulong LocalContentId => (ulong) Marshal.ReadInt64(Address.LocalContentId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The class facilitating Job Gauge data access
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JobGauges JobGauges;
|
public JobGauges JobGauges;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The class facilitating party list data access
|
/// The class facilitating party list data access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PartyList PartyList;
|
public PartyList PartyList;
|
||||||
|
|
||||||
|
|
@ -102,77 +51,76 @@ namespace Dalamud.Game.ClientState
|
||||||
/// Provides access to the button state of gamepad buttons in game.
|
/// Provides access to the button state of gamepad buttons in game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GamepadState GamepadState;
|
public GamepadState GamepadState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
|
/// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Condition Condition;
|
public Condition Condition;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The class facilitating target data access
|
/// The class facilitating target data access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Targets Targets;
|
public Targets Targets;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Event that gets fired when the current Territory changes.
|
||||||
|
/// </summary>
|
||||||
|
public EventHandler<ushort> TerritoryChanged;
|
||||||
|
|
||||||
|
private readonly Dalamud dalamud;
|
||||||
|
private readonly ClientStateAddressResolver address;
|
||||||
|
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||||
|
|
||||||
|
private bool lastConditionNone = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ClientState"/> class.
|
||||||
/// Set up client state access.
|
/// Set up client state access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dalamud">Dalamud instance</param>
|
/// <param name="dalamud">Dalamud instance.</param>
|
||||||
/// /// <param name="startInfo">StartInfo of the current Dalamud launch</param>
|
/// <param name="startInfo">StartInfo of the current Dalamud launch.</param>
|
||||||
/// <param name="scanner">Sig scanner</param>
|
/// <param name="scanner">Sig scanner.</param>
|
||||||
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner) {
|
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner)
|
||||||
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
Address = new ClientStateAddressResolver();
|
this.address = new ClientStateAddressResolver();
|
||||||
Address.Setup(scanner);
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
Log.Verbose("===== C L I E N T S T A T E =====");
|
||||||
|
|
||||||
this.ClientLanguage = startInfo.Language;
|
this.ClientLanguage = startInfo.Language;
|
||||||
|
|
||||||
this.Actors = new ActorTable(dalamud, Address);
|
this.Actors = new ActorTable(dalamud, this.address);
|
||||||
|
|
||||||
this.PartyList = new PartyList(dalamud, Address);
|
this.PartyList = new PartyList(dalamud, this.address);
|
||||||
|
|
||||||
this.JobGauges = new JobGauges(Address);
|
this.JobGauges = new JobGauges(this.address);
|
||||||
|
|
||||||
this.KeyState = new KeyState(Address, scanner.Module.BaseAddress);
|
this.KeyState = new KeyState(this.address, scanner.Module.BaseAddress);
|
||||||
|
|
||||||
this.GamepadState = new GamepadState(this.Address);
|
this.GamepadState = new GamepadState(this.address);
|
||||||
|
|
||||||
this.Condition = new Condition( Address );
|
this.Condition = new Condition(this.address);
|
||||||
|
|
||||||
this.Targets = new Targets(dalamud, Address);
|
this.Targets = new Targets(dalamud, this.address);
|
||||||
|
|
||||||
Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", Address.SetupTerritoryType);
|
Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", this.address.SetupTerritoryType);
|
||||||
|
|
||||||
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(Address.SetupTerritoryType,
|
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, new SetupTerritoryTypeDelegate(this.SetupTerritoryTypeDetour), this);
|
||||||
new SetupTerritoryTypeDelegate(SetupTerritoryTypeDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
dalamud.Framework.OnUpdateEvent += FrameworkOnOnUpdateEvent;
|
dalamud.Framework.OnUpdateEvent += this.FrameworkOnOnUpdateEvent;
|
||||||
dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
|
dalamud.NetworkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NetworkHandlersOnCfPop(object sender, ContentFinderCondition e) {
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
CfPop?.Invoke(this, e);
|
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
/// <summary>
|
||||||
this.GamepadState.Enable();
|
/// Event that fires when a property changes.
|
||||||
this.PartyList.Enable();
|
/// </summary>
|
||||||
this.setupTerritoryTypeHook.Enable();
|
#pragma warning disable CS0067
|
||||||
}
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
#pragma warning restore
|
||||||
public void Dispose() {
|
|
||||||
this.PartyList.Dispose();
|
|
||||||
this.setupTerritoryTypeHook.Dispose();
|
|
||||||
this.Actors.Dispose();
|
|
||||||
this.GamepadState.Dispose();
|
|
||||||
|
|
||||||
this.dalamud.Framework.OnUpdateEvent -= FrameworkOnOnUpdateEvent;
|
|
||||||
this.dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool lastConditionNone = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that fires when a character is logging in.
|
/// Event that fires when a character is logging in.
|
||||||
|
|
@ -184,24 +132,93 @@ namespace Dalamud.Game.ClientState
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler OnLogout;
|
public event EventHandler OnLogout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that gets fired when a duty is ready.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ContentFinderCondition> CfPop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the local player character, if one is present.
|
||||||
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
|
public PlayerCharacter LocalPlayer
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var actor = this.Actors[0];
|
||||||
|
|
||||||
|
if (actor is PlayerCharacter pc)
|
||||||
|
return pc;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content ID of the local character.
|
||||||
|
/// </summary>
|
||||||
|
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether a character is logged in.
|
/// Gets a value indicating whether a character is logged in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsLoggedIn { get; private set; }
|
public bool IsLoggedIn { get; private set; }
|
||||||
|
|
||||||
private void FrameworkOnOnUpdateEvent(Framework framework) {
|
/// <summary>
|
||||||
if (this.Condition.Any() && this.lastConditionNone == true) {
|
/// Enable this module.
|
||||||
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
this.GamepadState.Enable();
|
||||||
|
this.PartyList.Enable();
|
||||||
|
this.setupTerritoryTypeHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.PartyList.Dispose();
|
||||||
|
this.setupTerritoryTypeHook.Dispose();
|
||||||
|
this.Actors.Dispose();
|
||||||
|
this.GamepadState.Dispose();
|
||||||
|
|
||||||
|
this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;
|
||||||
|
this.dalamud.NetworkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
||||||
|
{
|
||||||
|
this.TerritoryType = terriType;
|
||||||
|
this.TerritoryChanged?.Invoke(this, terriType);
|
||||||
|
|
||||||
|
Log.Debug("TerritoryType changed: {0}", terriType);
|
||||||
|
|
||||||
|
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NetworkHandlersOnCfPop(object sender, ContentFinderCondition e)
|
||||||
|
{
|
||||||
|
this.CfPop?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrameworkOnOnUpdateEvent(Framework framework)
|
||||||
|
{
|
||||||
|
if (this.Condition.Any() && this.lastConditionNone == true)
|
||||||
|
{
|
||||||
Log.Debug("Is login");
|
Log.Debug("Is login");
|
||||||
this.lastConditionNone = false;
|
this.lastConditionNone = false;
|
||||||
this.IsLoggedIn = true;
|
this.IsLoggedIn = true;
|
||||||
OnLogin?.Invoke(this, null);
|
this.OnLogin?.Invoke(this, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.Condition.Any() && this.lastConditionNone == false) {
|
if (!this.Condition.Any() && this.lastConditionNone == false)
|
||||||
|
{
|
||||||
Log.Debug("Is logout");
|
Log.Debug("Is logout");
|
||||||
this.lastConditionNone = true;
|
this.lastConditionNone = true;
|
||||||
this.IsLoggedIn = false;
|
this.IsLoggedIn = false;
|
||||||
OnLogout?.Invoke(this, null);
|
this.OnLogout?.Invoke(this, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,88 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
using Dalamud.Game.Internal;
|
using Dalamud.Game.Internal;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
namespace Dalamud.Game.ClientState
|
||||||
{
|
{
|
||||||
public sealed class ClientStateAddressResolver : BaseAddressResolver {
|
/// <summary>
|
||||||
|
/// Client state memory address resolver.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||||
|
{
|
||||||
// Static offsets
|
// Static offsets
|
||||||
public IntPtr ActorTable { get; private set; }
|
|
||||||
//public IntPtr ViewportActorTable { get; private set; }
|
|
||||||
public IntPtr LocalContentId { get; private set; }
|
|
||||||
public IntPtr JobGaugeData { get; private set; }
|
|
||||||
public IntPtr KeyboardState { get; private set; }
|
|
||||||
public IntPtr TargetManager { get; private set; }
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
public IntPtr SetupTerritoryType { get; private set; }
|
|
||||||
//public IntPtr SomeActorTableAccess { get; private set; }
|
|
||||||
//public IntPtr PartyListUpdate { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Game function which polls the gamepads for data.
|
/// Gets the address of the actor table.
|
||||||
///
|
/// </summary>
|
||||||
|
public IntPtr ActorTable { get; private set; }
|
||||||
|
|
||||||
|
// public IntPtr ViewportActorTable { 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 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; }
|
||||||
|
|
||||||
|
// public IntPtr SomeActorTableAccess { get; private set; }
|
||||||
|
// public IntPtr PartyListUpdate { 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.
|
/// Called every frame, even when `Enable Gamepad` is off in the settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr GamepadPoll { get; private set; }
|
public IntPtr GamepadPoll { get; private set; }
|
||||||
|
|
||||||
public IntPtr ConditionFlags { get; private set; }
|
/// <summary>
|
||||||
|
/// Scan for and setup any configured address pointers.
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
/// </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
|
// 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;
|
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
|
||||||
//SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
|
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
|
||||||
ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
|
this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
|
||||||
|
|
||||||
LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
||||||
JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
|
this.JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
|
||||||
|
|
||||||
SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
|
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
|
||||||
|
|
||||||
// This resolves to a fixed offset only, without the base address added in, so GetStaticAddressFromSig() can't be used
|
// This resolves to a fixed offset only, without the base address added in, so GetStaticAddressFromSig() can't be used
|
||||||
KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
||||||
|
|
||||||
//PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
|
// PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
|
||||||
|
|
||||||
ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
|
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
|
||||||
|
|
||||||
TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
|
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
|
||||||
|
|
||||||
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
|
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
namespace Dalamud.Game.ClientState
|
||||||
{
|
{
|
||||||
|
|
@ -10,36 +7,49 @@ namespace Dalamud.Game.ClientState
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Condition
|
public class Condition
|
||||||
{
|
{
|
||||||
internal readonly IntPtr conditionArrayBase;
|
|
||||||
|
|
||||||
/// <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.
|
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int MaxConditionEntries = 100;
|
public const int MaxConditionEntries = 100;
|
||||||
|
|
||||||
internal Condition( ClientStateAddressResolver resolver )
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Condition"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
|
||||||
|
internal Condition(ClientStateAddressResolver resolver)
|
||||||
{
|
{
|
||||||
this.conditionArrayBase = resolver.ConditionFlags;
|
this.ConditionArrayBase = resolver.ConditionFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the condition array base pointer.
|
||||||
|
/// Would typically be private but is used in /xldata windows.
|
||||||
|
/// </summary>
|
||||||
|
internal IntPtr ConditionArrayBase { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check the value of a specific condition/state flag.
|
/// Check the value of a specific condition/state flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="flag">The condition flag to check</param>
|
/// <param name="flag">The condition flag to check.</param>
|
||||||
public unsafe bool this[ ConditionFlag flag ]
|
public unsafe bool this[ConditionFlag flag]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var idx = ( int )flag;
|
var idx = (int)flag;
|
||||||
|
|
||||||
if( idx > MaxConditionEntries || idx < 0 )
|
if (idx > MaxConditionEntries || idx < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return *( bool* )( this.conditionArrayBase + idx );
|
return *(bool*)(this.ConditionArrayBase + idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Any() {
|
/// <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++)
|
for (var i = 0; i < MaxConditionEntries; i++)
|
||||||
{
|
{
|
||||||
var typedCondition = (ConditionFlag)i;
|
var typedCondition = (ConditionFlag)i;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ namespace Dalamud.Game.ClientState
|
||||||
/// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to
|
/// 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.
|
/// 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>
|
||||||
public enum ConditionFlag {
|
public enum ConditionFlag
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unused.
|
/// Unused.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -27,9 +28,6 @@ namespace Dalamud.Game.ClientState
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Emoting = 3,
|
Emoting = 3,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while mounted.
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while mounted.
|
/// Unable to execute command while mounted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -94,15 +92,15 @@ namespace Dalamud.Game.ClientState
|
||||||
/// Unable to execute command while performing.
|
/// Unable to execute command while performing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Performing = 16,
|
Performing = 16,
|
||||||
|
|
||||||
//Unknown17 = 17,
|
// Unknown17 = 17,
|
||||||
//Unknown18 = 18,
|
// Unknown18 = 18,
|
||||||
//Unknown19 = 19,
|
// Unknown19 = 19,
|
||||||
//Unknown20 = 20,
|
// Unknown20 = 20,
|
||||||
//Unknown21 = 21,
|
// Unknown21 = 21,
|
||||||
//Unknown22 = 22,
|
// Unknown22 = 22,
|
||||||
//Unknown23 = 23,
|
// Unknown23 = 23,
|
||||||
//Unknown24 = 24,
|
// Unknown24 = 24,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command while occupied.
|
||||||
|
|
@ -199,8 +197,8 @@ namespace Dalamud.Game.ClientState
|
||||||
/// Unable to execute command while fishing.
|
/// Unable to execute command while fishing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Fishing = 43,
|
Fishing = 43,
|
||||||
|
|
||||||
//Unknown44 = 44,
|
// Unknown44 = 44,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while between areas.
|
/// Unable to execute command while between areas.
|
||||||
|
|
@ -211,8 +209,8 @@ namespace Dalamud.Game.ClientState
|
||||||
/// Unable to execute command while stealthed.
|
/// Unable to execute command while stealthed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Stealthed = 46,
|
Stealthed = 46,
|
||||||
|
|
||||||
//Unknown47 = 47,
|
// Unknown47 = 47,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while jumping.
|
/// Unable to execute command while jumping.
|
||||||
|
|
@ -399,8 +397,8 @@ namespace Dalamud.Game.ClientState
|
||||||
/// Unable to execute command while participating in a cross-world party or alliance.
|
/// Unable to execute command while participating in a cross-world party or alliance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
||||||
|
|
||||||
//Unknown85 = 85,
|
// Unknown85 = 85,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while playing duty record.
|
/// Unable to execute command while playing duty record.
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ namespace Dalamud.Game.ClientState
|
||||||
/// Gets or sets a value indicating whether detour should block gamepad input for game.
|
/// Gets or sets a value indicating whether detour should block gamepad input for game.
|
||||||
///
|
///
|
||||||
/// Ideally, we would use
|
/// Ideally, we would use
|
||||||
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
|
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
|
||||||
/// but this has a race condition during load with the detour which sets up ImGui
|
/// 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.
|
/// and throws if our detour gets called before the other.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,26 @@
|
||||||
using Serilog;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
namespace Dalamud.Game.ClientState
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wrapper around the game keystate buffer, which contains the pressed state for
|
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
|
||||||
/// all keyboard keys, indexed by virtual vkCode
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class KeyState
|
public class KeyState
|
||||||
{
|
{
|
||||||
private IntPtr bufferBase;
|
|
||||||
|
|
||||||
// The array is accessed in a way that this limit doesn't appear to exist
|
// 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
|
// but there is other state data past this point, and keys beyond here aren't
|
||||||
// generally valid for most things anyway
|
// generally valid for most things anyway
|
||||||
private const int MaxKeyCodeIndex = 0xA0;
|
private const int MaxKeyCodeIndex = 0xA0;
|
||||||
|
private IntPtr bufferBase;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="KeyState"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||||
|
/// <param name="moduleBaseAddress">The base address of the main process module.</param>
|
||||||
public KeyState(ClientStateAddressResolver addressResolver, IntPtr moduleBaseAddress)
|
public KeyState(ClientStateAddressResolver addressResolver, IntPtr moduleBaseAddress)
|
||||||
{
|
{
|
||||||
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
||||||
|
|
@ -33,10 +37,10 @@ namespace Dalamud.Game.ClientState
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (vkCode< 0 || vkCode > MaxKeyCodeIndex)
|
if (vkCode < 0 || vkCode > MaxKeyCodeIndex)
|
||||||
throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}");
|
throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}");
|
||||||
|
|
||||||
return (Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0);
|
return Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
set
|
set
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,139 @@
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
|
// using Dalamud.Hooking;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
namespace Dalamud.Game.ClientState
|
||||||
{
|
{
|
||||||
public class PartyList : IReadOnlyCollection<PartyMember>, ICollection, IDisposable
|
/// <summary>
|
||||||
|
/// This class represents the members of your party.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class PartyList
|
||||||
{
|
{
|
||||||
private ClientStateAddressResolver Address { get; }
|
private readonly Dalamud dalamud;
|
||||||
private Dalamud dalamud;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
|
// private bool isReady = false;
|
||||||
|
// private IntPtr partyListBegin;
|
||||||
|
// private Hook<PartyListUpdateDelegate> partyListUpdateHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PartyList"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||||
|
public PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
||||||
|
{
|
||||||
|
this.address = addressResolver;
|
||||||
|
this.dalamud = dalamud;
|
||||||
|
// this.partyListUpdateHook = new Hook<PartyListUpdateDelegate>(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
|
||||||
|
}
|
||||||
|
|
||||||
private delegate long PartyListUpdateDelegate(IntPtr structBegin, long param2, char param3);
|
private delegate long PartyListUpdateDelegate(IntPtr structBegin, long param2, char param3);
|
||||||
|
|
||||||
private Hook<PartyListUpdateDelegate> partyListUpdateHook;
|
/// <summary>
|
||||||
private IntPtr partyListBegin;
|
/// Gets the length of the PartyList.
|
||||||
private bool isReady = false;
|
/// </summary>
|
||||||
|
public int Length => 0; // !this.isReady ? 0 : Marshal.ReadByte(this.partyListBegin + 0xF0);
|
||||||
|
|
||||||
public PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
/// <summary>
|
||||||
|
/// Get the nth party member.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the party member.</param>
|
||||||
|
/// <returns>The party member.</returns>
|
||||||
|
public PartyMember this[int index]
|
||||||
{
|
{
|
||||||
Address = addressResolver;
|
get
|
||||||
this.dalamud = dalamud;
|
{
|
||||||
//this.partyListUpdateHook = new Hook<PartyListUpdateDelegate>(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
|
return null;
|
||||||
|
// if (!this.isReady)
|
||||||
|
// return null;
|
||||||
|
// if (index >= this.Length)
|
||||||
|
// return null;
|
||||||
|
// var tblIndex = this.partyListBegin + (index * 24);
|
||||||
|
// var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
|
||||||
|
// return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable this module.
|
||||||
|
/// </summary>
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
// TODO Fix for 5.3
|
// TODO Fix for 5.3
|
||||||
//this.partyListUpdateHook.Enable();
|
// this.partyListUpdateHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
//if (!this.isReady)
|
// if (!this.isReady)
|
||||||
// this.partyListUpdateHook.Dispose();
|
// this.partyListUpdateHook.Dispose();
|
||||||
this.isReady = false;
|
// this.isReady = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
|
// private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
|
||||||
{
|
// {
|
||||||
var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
|
// var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
|
||||||
this.partyListBegin = structBegin + 0xB48;
|
// this.partyListBegin = structBegin + 0xB48;
|
||||||
this.partyListUpdateHook.Dispose();
|
// this.partyListUpdateHook.Dispose();
|
||||||
this.isReady = true;
|
// this.isReady = true;
|
||||||
return result;
|
// return result;
|
||||||
}
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
public PartyMember this[int index]
|
/// <summary>
|
||||||
{
|
/// Implements IReadOnlyCollection, IEnumerable.
|
||||||
get {
|
/// </summary>
|
||||||
if (!this.isReady)
|
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
|
||||||
return null;
|
{
|
||||||
if (index >= Length)
|
/// <inheritdoc/>
|
||||||
return null;
|
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
||||||
var tblIndex = partyListBegin + index * 24;
|
|
||||||
var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
|
|
||||||
return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(Array array, int index)
|
/// <inheritdoc/>
|
||||||
|
public IEnumerator<PartyMember> GetEnumerator()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Length; i++)
|
for (var i = 0; i < this.Length; i++)
|
||||||
{
|
{
|
||||||
array.SetValue(this[i], index);
|
if (this[i] != null)
|
||||||
index++;
|
{
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<PartyMember> GetEnumerator() {
|
|
||||||
for (var i = 0; i < Length; i++) {
|
|
||||||
if (this[i] != null) {
|
|
||||||
yield return this[i];
|
yield return this[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
/// <inheritdoc/>
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
public int Length => !this.isReady ? 0 : Marshal.ReadByte(partyListBegin + 0xF0);
|
/// <summary>
|
||||||
|
/// Implements ICollection.
|
||||||
int IReadOnlyCollection<PartyMember>.Count => Length;
|
/// </summary>
|
||||||
|
public sealed partial class PartyList : ICollection
|
||||||
public int Count => Length;
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count => this.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public object SyncRoot => this;
|
public object SyncRoot => this;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public bool IsSynchronized => false;
|
public bool IsSynchronized => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
array.SetValue(this[i], index);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,245 @@ using Dalamud.Game.ClientState.Actors;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs
|
namespace Dalamud.Game.ClientState.Structs
|
||||||
{
|
{
|
||||||
public class ActorOffsets
|
/// <summary>
|
||||||
|
/// Native memory representation of an FFXIV actor.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Explicit, Pack = 2)]
|
||||||
|
public struct Actor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The actor name.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.Name)]
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actor's internal id.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.ActorId)]
|
||||||
|
public int ActorId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actor's data id.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.DataId)]
|
||||||
|
public int DataId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actor's owner id. This is useful for pets, summons, and the like.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.OwnerId)]
|
||||||
|
public int OwnerId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type or kind of actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.ObjectKind)]
|
||||||
|
public ObjectKind ObjectKind;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sub-type or sub-kind of actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.SubKind)]
|
||||||
|
public byte SubKind;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the actor is friendly.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.IsFriendly)]
|
||||||
|
public bool IsFriendly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The horizontal distance in game units from the player.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerX)]
|
||||||
|
public byte YalmDistanceFromPlayerX;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The player target status.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is some kind of enum.
|
||||||
|
/// </remarks>
|
||||||
|
[FieldOffset(ActorOffsets.PlayerTargetStatus)]
|
||||||
|
public byte PlayerTargetStatus;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The vertical distance in game units from the player.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerY)]
|
||||||
|
public byte YalmDistanceFromPlayerY;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The (X,Z,Y) position of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.Position)]
|
||||||
|
public Position3 Position;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The rotation of the actor.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The rotation is around the vertical axis (yaw), from -pi to pi radians.
|
||||||
|
/// </remarks>
|
||||||
|
[FieldOffset(ActorOffsets.Rotation)]
|
||||||
|
public float Rotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The hitbox radius of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.HitboxRadius)]
|
||||||
|
public float HitboxRadius;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current HP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentHp)]
|
||||||
|
public int CurrentHp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max HP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.MaxHp)]
|
||||||
|
public int MaxHp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current MP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentMp)]
|
||||||
|
public int CurrentMp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max MP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.MaxMp)]
|
||||||
|
public short MaxMp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current GP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentGp)]
|
||||||
|
public short CurrentGp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max GP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.MaxGp)]
|
||||||
|
public short MaxGp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current CP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentCp)]
|
||||||
|
public short CurrentCp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max CP of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.MaxCp)]
|
||||||
|
public short MaxCp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The class-job of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.ClassJob)]
|
||||||
|
public byte ClassJob;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The level of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.Level)]
|
||||||
|
public byte Level;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The (player character) actor ID being targeted by the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.PlayerCharacterTargetActorId)]
|
||||||
|
public int PlayerCharacterTargetActorId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The customization byte/bitfield of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.Customize)]
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
|
||||||
|
public byte[] Customize;
|
||||||
|
|
||||||
|
// Normally pack=2 should work, but ByTVal or Injection breaks this.
|
||||||
|
// [FieldOffset(ActorOffsets.CompanyTag)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public string CompanyTag;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The (battle npc) actor ID being targeted by the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.BattleNpcTargetActorId)]
|
||||||
|
public int BattleNpcTargetActorId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name ID of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.NameId)]
|
||||||
|
public int NameId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current world ID of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentWorld)]
|
||||||
|
public ushort CurrentWorld;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The home world ID of the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.HomeWorld)]
|
||||||
|
public ushort HomeWorld;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the actor is currently casting.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.IsCasting)]
|
||||||
|
public bool IsCasting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the actor is currently casting (dup?).
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.IsCasting2)]
|
||||||
|
public bool IsCasting2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spell action ID currently being cast by the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentCastSpellActionId)]
|
||||||
|
public uint CurrentCastSpellActionId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actor ID of the target currently being cast at by the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentCastTargetActorId)]
|
||||||
|
public uint CurrentCastTargetActorId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current casting time of the spell being cast by the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.CurrentCastTime)]
|
||||||
|
public float CurrentCastTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total casting time of the spell being cast by the actor.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.TotalCastTime)]
|
||||||
|
public float TotalCastTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The array of status effects that the actor is currently affected by.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(ActorOffsets.UIStatusEffects)]
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||||
|
public StatusEffect[] UIStatusEffects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Memory offsets for the <see cref="Actor"/> type.
|
||||||
|
/// </summary>
|
||||||
|
public static class ActorOffsets
|
||||||
{
|
{
|
||||||
// Reference https://github.com/FFXIVAPP/sharlayan-resources/blob/master/structures/5.4/x64.json for more
|
// Reference https://github.com/FFXIVAPP/sharlayan-resources/blob/master/structures/5.4/x64.json for more
|
||||||
public const int Name = 48; // 0x0030
|
public const int Name = 48; // 0x0030
|
||||||
|
|
@ -48,51 +286,4 @@ namespace Dalamud.Game.ClientState.Structs
|
||||||
public const int TotalCastTime = 0x1BB8;
|
public const int TotalCastTime = 0x1BB8;
|
||||||
public const int UIStatusEffects = 0x19F8;
|
public const int UIStatusEffects = 0x19F8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Native memory representation of a FFXIV actor.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Explicit, Pack = 2)]
|
|
||||||
public struct Actor
|
|
||||||
{
|
|
||||||
[FieldOffset(ActorOffsets.Name)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)] public string Name;
|
|
||||||
[FieldOffset(ActorOffsets.ActorId)] public int ActorId;
|
|
||||||
[FieldOffset(ActorOffsets.DataId)] public int DataId;
|
|
||||||
[FieldOffset(ActorOffsets.OwnerId)] public int OwnerId;
|
|
||||||
[FieldOffset(ActorOffsets.ObjectKind)] public ObjectKind ObjectKind;
|
|
||||||
[FieldOffset(ActorOffsets.SubKind)] public byte SubKind;
|
|
||||||
[FieldOffset(ActorOffsets.IsFriendly)] public bool IsFriendly;
|
|
||||||
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerX)] public byte YalmDistanceFromPlayerX; // Demo says one of these is x distance
|
|
||||||
[FieldOffset(ActorOffsets.PlayerTargetStatus)] public byte PlayerTargetStatus; // This is some kind of enum
|
|
||||||
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerY)] public byte YalmDistanceFromPlayerY; // and the other is z distance
|
|
||||||
[FieldOffset(ActorOffsets.Position)] public Position3 Position;
|
|
||||||
[FieldOffset(ActorOffsets.Rotation)] public float Rotation; // Rotation around the vertical axis (yaw), from -pi to pi radians
|
|
||||||
[FieldOffset(ActorOffsets.HitboxRadius)] public float HitboxRadius;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentHp)] public int CurrentHp;
|
|
||||||
[FieldOffset(ActorOffsets.MaxHp)] public int MaxHp;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentMp)] public int CurrentMp;
|
|
||||||
[FieldOffset(ActorOffsets.MaxMp)] public short MaxMp;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentGp)] public short CurrentGp;
|
|
||||||
[FieldOffset(ActorOffsets.MaxGp)] public short MaxGp;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentCp)] public short CurrentCp;
|
|
||||||
[FieldOffset(ActorOffsets.MaxCp)] public short MaxCp;
|
|
||||||
[FieldOffset(ActorOffsets.ClassJob)] public byte ClassJob;
|
|
||||||
[FieldOffset(ActorOffsets.Level)] public byte Level;
|
|
||||||
[FieldOffset(ActorOffsets.PlayerCharacterTargetActorId)] public int PlayerCharacterTargetActorId;
|
|
||||||
[FieldOffset(ActorOffsets.Customize)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)] public byte[] Customize;
|
|
||||||
|
|
||||||
// Normally pack=2 should work, but ByTVal or Injection breaks this.
|
|
||||||
// [FieldOffset(ActorOffsets.CompanyTag)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public string CompanyTag;
|
|
||||||
[FieldOffset(ActorOffsets.BattleNpcTargetActorId)] public int BattleNpcTargetActorId;
|
|
||||||
[FieldOffset(ActorOffsets.NameId)] public int NameId;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentWorld)] public ushort CurrentWorld;
|
|
||||||
[FieldOffset(ActorOffsets.HomeWorld)] public ushort HomeWorld;
|
|
||||||
[FieldOffset(ActorOffsets.IsCasting)] public bool IsCasting;
|
|
||||||
[FieldOffset(ActorOffsets.IsCasting2)] public bool IsCasting2;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentCastSpellActionId)] public uint CurrentCastSpellActionId;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentCastTargetActorId)] public uint CurrentCastTargetActorId;
|
|
||||||
[FieldOffset(ActorOffsets.CurrentCastTime)] public float CurrentCastTime;
|
|
||||||
[FieldOffset(ActorOffsets.TotalCastTime)] public float TotalCastTime;
|
|
||||||
[FieldOffset(ActorOffsets.UIStatusEffects)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public StatusEffect[] UIStatusEffects;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
#pragma warning disable SA1402 // File may only contain a single type
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge
|
namespace Dalamud.Game.ClientState.Structs.JobGauge
|
||||||
{
|
{
|
||||||
#region AST
|
#region AST
|
||||||
|
|
@ -270,3 +272,5 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1402 // File may only contain a single type
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,26 @@
|
||||||
using Dalamud.Game.ClientState.Actors;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using Dalamud.Game.ClientState.Actors;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs
|
namespace Dalamud.Game.ClientState.Structs
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This represents a native PartyMember class in memory.
|
||||||
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public struct PartyMember
|
public struct PartyMember
|
||||||
{
|
{
|
||||||
[FieldOffset(0x0)] public IntPtr namePtr;
|
[FieldOffset(0x0)]
|
||||||
[FieldOffset(0x8)] public long unknown;
|
public IntPtr namePtr;
|
||||||
[FieldOffset(0x10)] public int actorId;
|
|
||||||
[FieldOffset(0x14)] public ObjectKind objectKind;
|
[FieldOffset(0x8)]
|
||||||
|
public long unknown;
|
||||||
|
|
||||||
|
[FieldOffset(0x10)]
|
||||||
|
public int actorId;
|
||||||
|
|
||||||
|
[FieldOffset(0x14)]
|
||||||
|
public ObjectKind objectKind;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs
|
namespace Dalamud.Game.ClientState.Structs
|
||||||
|
|
@ -9,10 +8,29 @@ namespace Dalamud.Game.ClientState.Structs
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct StatusEffect
|
public struct StatusEffect
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The effect ID.
|
||||||
|
/// </summary>
|
||||||
public short EffectId;
|
public short EffectId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many stacks are present.
|
||||||
|
/// </summary>
|
||||||
public byte StackCount;
|
public byte StackCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Additional parameters.
|
||||||
|
/// </summary>
|
||||||
public byte Param;
|
public byte Param;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The duration remaining.
|
||||||
|
/// </summary>
|
||||||
public float Duration;
|
public float Duration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the actor that caused this effect.
|
||||||
|
/// </summary>
|
||||||
public int OwnerId;
|
public int OwnerId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Dalamud.Game.Command {
|
namespace Dalamud.Game.Command
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class describes a registered command.
|
/// This class describes a registered command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CommandInfo {
|
public sealed class CommandInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CommandInfo"/> class.
|
||||||
|
/// Create a new CommandInfo with the provided handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The method to call when the command is run.</param>
|
||||||
|
public CommandInfo(HandlerDelegate handler)
|
||||||
|
{
|
||||||
|
this.Handler = handler;
|
||||||
|
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The function to be executed when the command is dispatched.
|
/// The function to be executed when the command is dispatched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -13,29 +26,23 @@ namespace Dalamud.Game.Command {
|
||||||
public delegate void HandlerDelegate(string command, string arguments);
|
public delegate void HandlerDelegate(string command, string arguments);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
|
/// Gets a <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HandlerDelegate Handler { get; }
|
public HandlerDelegate Handler { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The help message for this command.
|
/// Gets or sets the help message for this command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string HelpMessage { get; set; } = string.Empty;
|
public string HelpMessage { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If this command should be shown in the help output.
|
/// Gets or sets a value indicating whether if this command should be shown in the help output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShowInHelp { get; set; } = true;
|
public bool ShowInHelp { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new CommandInfo with the provided handler.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handler"></param>
|
|
||||||
public CommandInfo(HandlerDelegate handler) {
|
|
||||||
Handler = handler;
|
|
||||||
LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the assembly responsible for this command.
|
||||||
|
/// </summary>
|
||||||
internal string LoaderAssemblyName { get; set; } = string.Empty;
|
internal string LoaderAssemblyName { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,45 +2,37 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Command {
|
namespace Dalamud.Game.Command
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class manages registered in-game slash commands.
|
/// This class manages registered in-game slash commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CommandManager {
|
public sealed class CommandManager
|
||||||
|
{
|
||||||
private readonly Dalamud dalamud;
|
private readonly Dalamud dalamud;
|
||||||
|
private readonly Dictionary<string, CommandInfo> commandMap = new();
|
||||||
private readonly Dictionary<string, CommandInfo> commandMap = new Dictionary<string, CommandInfo>();
|
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||||
|
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||||
/// <summary>
|
private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
||||||
/// Read-only list of all registered commands.
|
private readonly Regex commandRegexFr = new(@"^La commande texte “(?<command>.+)” n'existe pas\.$", RegexOptions.Compiled);
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyDictionary<string, CommandInfo> Commands =>
|
|
||||||
new ReadOnlyDictionary<string, CommandInfo>(this.commandMap);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexEn =
|
|
||||||
new Regex(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexJp = new Regex(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexDe =
|
|
||||||
new Regex(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexFr =
|
|
||||||
new Regex(@"^La commande texte “(?<command>.+)” n'existe pas\.$",
|
|
||||||
RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex currentLangCommandRegex;
|
private readonly Regex currentLangCommandRegex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public CommandManager(Dalamud dalamud, ClientLanguage language) {
|
/// Initializes a new instance of the <see cref="CommandManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
/// <param name="language">The client language requested.</param>
|
||||||
|
public CommandManager(Dalamud dalamud, ClientLanguage language)
|
||||||
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
switch (language) {
|
switch (language)
|
||||||
|
{
|
||||||
case ClientLanguage.Japanese:
|
case ClientLanguage.Japanese:
|
||||||
this.currentLangCommandRegex = this.commandRegexJp;
|
this.currentLangCommandRegex = this.commandRegexJp;
|
||||||
break;
|
break;
|
||||||
|
|
@ -55,40 +47,42 @@ namespace Dalamud.Game.Command {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
|
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += this.OnCheckMessageHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
|
/// <summary>
|
||||||
if (type == XivChatType.ErrorMessage && senderId == 0) {
|
/// Gets a read-only list of all registered commands.
|
||||||
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
/// </summary>
|
||||||
if (cmdMatch.Success) {
|
public ReadOnlyDictionary<string, CommandInfo> Commands => new(this.commandMap);
|
||||||
// Yes, it's a chat command.
|
|
||||||
var command = cmdMatch.Value;
|
|
||||||
if (ProcessCommand(command)) isHandled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process a command in full.
|
/// Process a command in full.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="content">The full command string.</param>
|
/// <param name="content">The full command string.</param>
|
||||||
/// <returns>True if the command was found and dispatched.</returns>
|
/// <returns>True if the command was found and dispatched.</returns>
|
||||||
public bool ProcessCommand(string content) {
|
public bool ProcessCommand(string content)
|
||||||
|
{
|
||||||
string command;
|
string command;
|
||||||
string argument;
|
string argument;
|
||||||
|
|
||||||
var separatorPosition = content.IndexOf(' ');
|
var separatorPosition = content.IndexOf(' ');
|
||||||
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length) {
|
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length)
|
||||||
|
{
|
||||||
// If no space was found or ends with the space. Process them as a no argument
|
// If no space was found or ends with the space. Process them as a no argument
|
||||||
if (separatorPosition + 1 >= content.Length) {
|
if (separatorPosition + 1 >= content.Length)
|
||||||
|
{
|
||||||
// Remove the trailing space
|
// Remove the trailing space
|
||||||
command = content.Substring(0, separatorPosition);
|
command = content.Substring(0, separatorPosition);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
command = content;
|
command = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
argument = string.Empty;
|
argument = string.Empty;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// e.g.)
|
// e.g.)
|
||||||
// /testcommand arg1
|
// /testcommand arg1
|
||||||
// => Total of 17 chars
|
// => Total of 17 chars
|
||||||
|
|
@ -98,13 +92,13 @@ namespace Dalamud.Game.Command {
|
||||||
command = content.Substring(0, separatorPosition);
|
command = content.Substring(0, separatorPosition);
|
||||||
|
|
||||||
var argStart = separatorPosition + 1;
|
var argStart = separatorPosition + 1;
|
||||||
argument = content.Substring(argStart, content.Length - argStart);
|
argument = content[argStart..];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
DispatchCommand(command, argument, handler);
|
this.DispatchCommand(command, argument, handler);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,12 +108,15 @@ namespace Dalamud.Game.Command {
|
||||||
/// <param name="command">The command to dispatch.</param>
|
/// <param name="command">The command to dispatch.</param>
|
||||||
/// <param name="argument">The provided arguments.</param>
|
/// <param name="argument">The provided arguments.</param>
|
||||||
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
||||||
public void DispatchCommand(string command, string argument, CommandInfo info) {
|
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
info.Handler(command, argument);
|
info.Handler(command, argument);
|
||||||
} catch (Exception ex) {
|
}
|
||||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command,
|
catch (Exception ex)
|
||||||
argument);
|
{
|
||||||
|
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,13 +126,17 @@ namespace Dalamud.Game.Command {
|
||||||
/// <param name="command">The command to register.</param>
|
/// <param name="command">The command to register.</param>
|
||||||
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
||||||
/// <returns>If adding was successful.</returns>
|
/// <returns>If adding was successful.</returns>
|
||||||
public bool AddHandler(string command, CommandInfo info) {
|
public bool AddHandler(string command, CommandInfo info)
|
||||||
|
{
|
||||||
if (info == null) throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
if (info == null) throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
this.commandMap.Add(command, info);
|
this.commandMap.Add(command, info);
|
||||||
return true;
|
return true;
|
||||||
} catch (ArgumentException) {
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
Log.Error("Command {CommandName} is already registered.", command);
|
Log.Error("Command {CommandName} is already registered.", command);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -146,8 +147,23 @@ namespace Dalamud.Game.Command {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="command">The command to remove.</param>
|
/// <param name="command">The command to remove.</param>
|
||||||
/// <returns>If the removal was successful.</returns>
|
/// <returns>If the removal was successful.</returns>
|
||||||
public bool RemoveHandler(string command) {
|
public bool RemoveHandler(string command)
|
||||||
|
{
|
||||||
return this.commandMap.Remove(command);
|
return this.commandMap.Remove(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
|
{
|
||||||
|
if (type == XivChatType.ErrorMessage && senderId == 0)
|
||||||
|
{
|
||||||
|
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
||||||
|
if (cmdMatch.Success)
|
||||||
|
{
|
||||||
|
// Yes, it's a chat command.
|
||||||
|
var command = cmdMatch.Value;
|
||||||
|
if (this.ProcessCommand(command)) isHandled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,118 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using EasyHook;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal
|
namespace Dalamud.Game.Internal
|
||||||
{
|
{
|
||||||
public class AntiDebug : IDisposable
|
/// <summary>
|
||||||
|
/// This class disables anti-debug functionality in the game client.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AntiDebug
|
||||||
{
|
{
|
||||||
private IntPtr DebugCheckAddress { get; set; }
|
|
||||||
|
|
||||||
public bool IsEnabled { get; private set; }
|
|
||||||
|
|
||||||
public AntiDebug(SigScanner scanner) {
|
|
||||||
try {
|
|
||||||
DebugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41");
|
|
||||||
} catch (KeyNotFoundException) {
|
|
||||||
DebugCheckAddress = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Verbose("DebugCheck address {DebugCheckAddress}", DebugCheckAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
|
private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
|
||||||
private byte[] original;
|
private byte[] original;
|
||||||
|
private IntPtr debugCheckAddress;
|
||||||
|
|
||||||
public void Enable() {
|
/// <summary>
|
||||||
this.original = new byte[this.nop.Length];
|
/// Initializes a new instance of the <see cref="AntiDebug"/> class.
|
||||||
if (DebugCheckAddress != IntPtr.Zero && !IsEnabled) {
|
/// </summary>
|
||||||
Log.Information($"Overwriting Debug Check @ 0x{DebugCheckAddress.ToInt64():X}");
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
SafeMemory.ReadBytes(DebugCheckAddress, this.nop.Length, out this.original);
|
public AntiDebug(SigScanner scanner)
|
||||||
SafeMemory.WriteBytes(DebugCheckAddress, this.nop);
|
{
|
||||||
} else {
|
try
|
||||||
Log.Information("DebugCheck already overwritten?");
|
{
|
||||||
|
this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41");
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
this.debugCheckAddress = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsEnabled = true;
|
Log.Verbose("DebugCheck address {DebugCheckAddress}", this.debugCheckAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
/// <summary>
|
||||||
//if (this.DebugCheckAddress != IntPtr.Zero && this.original != null)
|
/// Gets a value indicating whether the anti-debugging is enabled.
|
||||||
// Marshal.Copy(this.original, 0, DebugCheckAddress, this.nop.Length);
|
/// </summary>
|
||||||
|
public bool IsEnabled { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the anti-debugging by overwriting code in memory.
|
||||||
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
this.original = new byte[this.nop.Length];
|
||||||
|
if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled)
|
||||||
|
{
|
||||||
|
Log.Information($"Overwriting debug check @ 0x{this.debugCheckAddress.ToInt64():X}");
|
||||||
|
SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original);
|
||||||
|
SafeMemory.WriteBytes(this.debugCheckAddress, this.nop);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Information("Debug check already overwritten?");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable the anti-debugging by reverting the overwritten code in memory.
|
||||||
|
/// </summary>
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
if (this.debugCheckAddress != IntPtr.Zero && this.original != null)
|
||||||
|
{
|
||||||
|
Log.Information($"Reverting debug check @ 0x{this.debugCheckAddress.ToInt64():X}");
|
||||||
|
SafeMemory.WriteBytes(this.debugCheckAddress, this.original);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Information("Debug check was not overwritten?");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.IsEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementing IDisposable.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AntiDebug : IDisposable
|
||||||
|
{
|
||||||
|
private bool disposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes an instance of the <see cref="AntiDebug"/> class.
|
||||||
|
/// </summary>
|
||||||
|
~AntiDebug() => this.Dispose(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">If this was disposed through calling Dispose() or from being finalized.</param>
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (this.disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded.
|
||||||
|
// If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the
|
||||||
|
// check in either situation anyways.
|
||||||
|
// this.Disable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,57 +3,103 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal {
|
namespace Dalamud.Game.Internal
|
||||||
public abstract class BaseAddressResolver {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base memory address resolver.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseAddressResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A list of memory addresses that were found, to list in /xldata.
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, List<(string, IntPtr)>> DebugScannedValues = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
||||||
|
/// </summary>
|
||||||
protected bool IsResolved { get; set; }
|
protected bool IsResolved { get; set; }
|
||||||
|
|
||||||
public static Dictionary<string, List<(string, IntPtr)>> DebugScannedValues = new Dictionary<string, List<(string, IntPtr)>>();
|
/// <summary>
|
||||||
|
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||||
public void Setup(SigScanner scanner) {
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
public void Setup(SigScanner scanner)
|
||||||
|
{
|
||||||
// Because C# don't allow to call virtual function while in ctor
|
// Because C# don't allow to call virtual function while in ctor
|
||||||
// we have to do this shit :\
|
// we have to do this shit :\
|
||||||
|
|
||||||
if (IsResolved) {
|
if (this.IsResolved)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scanner.Is32BitProcess) {
|
|
||||||
Setup32Bit(scanner);
|
|
||||||
} else {
|
|
||||||
Setup64Bit(scanner);
|
|
||||||
}
|
|
||||||
SetupInternal(scanner);
|
|
||||||
|
|
||||||
var className = GetType().Name;
|
if (scanner.Is32BitProcess)
|
||||||
|
{
|
||||||
|
this.Setup32Bit(scanner);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Setup64Bit(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SetupInternal(scanner);
|
||||||
|
|
||||||
|
var className = this.GetType().Name;
|
||||||
DebugScannedValues[className] = new List<(string, IntPtr)>();
|
DebugScannedValues[className] = new List<(string, IntPtr)>();
|
||||||
|
|
||||||
foreach (var property in GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) {
|
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
||||||
DebugScannedValues[className].Add((property.Name, (IntPtr) property.GetValue(this)));
|
{
|
||||||
|
DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
IsResolved = true;
|
this.IsResolved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Setup32Bit(SigScanner scanner) {
|
/// <summary>
|
||||||
|
/// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The delegate to marshal the function pointer to.</typeparam>
|
||||||
|
/// <param name="address">The address of the virtual table.</param>
|
||||||
|
/// <param name="vtableOffset">The offset from address to the vtable pointer.</param>
|
||||||
|
/// <param name="count">The vfunc index.</param>
|
||||||
|
/// <returns>A delegate function pointer that can be invoked.</returns>
|
||||||
|
public T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.");
|
throw new NotSupportedException("32 bit version is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Setup64Bit(SigScanner sig) {
|
/// <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.");
|
throw new NotSupportedException("64 bit version is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
public T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class {
|
protected virtual void SetupInternal(SigScanner scanner)
|
||||||
// Get vtable
|
{
|
||||||
var vtable = Marshal.ReadIntPtr(address, vtableOffset);
|
// Do nothing
|
||||||
|
|
||||||
// Get an address to the function
|
|
||||||
var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
|
|
||||||
|
|
||||||
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,20 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.DXGI {
|
namespace Dalamud.Game.Internal.DXGI
|
||||||
public interface ISwapChainAddressResolver {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface binding for the address resolvers that attempt to find native D3D11 methods.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISwapChainAddressResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the address of the native D3D11.Present method.
|
||||||
|
/// </summary>
|
||||||
IntPtr Present { get; set; }
|
IntPtr Present { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the address of the native D3D11.ResizeBuffers method.
|
||||||
|
/// </summary>
|
||||||
IntPtr ResizeBuffers { get; set; }
|
IntPtr ResizeBuffers { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,23 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.DXGI
|
namespace Dalamud.Game.Internal.DXGI
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI.
|
||||||
|
/// </summary>
|
||||||
public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver
|
public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
public IntPtr Present { get; set; }
|
public IntPtr Present { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public IntPtr ResizeBuffers { get; set; }
|
public IntPtr ResizeBuffers { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "dxgi.dll");
|
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "dxgi.dll");
|
||||||
|
|
@ -22,9 +27,9 @@ namespace Dalamud.Game.Internal.DXGI
|
||||||
var scanner = new SigScanner(module);
|
var scanner = new SigScanner(module);
|
||||||
|
|
||||||
// This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function.
|
// This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function.
|
||||||
Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37;
|
this.Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37;
|
||||||
|
|
||||||
ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24");
|
this.ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,69 @@
|
||||||
using SharpDX.Direct3D;
|
|
||||||
using SharpDX.Direct3D11;
|
|
||||||
using SharpDX.DXGI;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using SharpDX.Windows;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using SharpDX.Direct3D;
|
||||||
|
using SharpDX.Direct3D11;
|
||||||
|
using SharpDX.DXGI;
|
||||||
|
|
||||||
using Device = SharpDX.Direct3D11.Device;
|
using Device = SharpDX.Direct3D11.Device;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.DXGI
|
namespace Dalamud.Game.Internal.DXGI
|
||||||
{
|
{
|
||||||
/*
|
/// <summary>
|
||||||
* This method of getting the SwapChain Addresses is currently not used.
|
/// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it.
|
||||||
* If the normal AddressResolver(SigScanner) fails, we should use it as a fallback.(Linux?)
|
/// </summary>
|
||||||
*/
|
/// <remarks>
|
||||||
|
/// If the normal signature based method of resolution fails, this is the backup.
|
||||||
|
/// </remarks>
|
||||||
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||||
{
|
{
|
||||||
private const int DxgiSwapchainMethodCount = 18;
|
private const int DxgiSwapchainMethodCount = 18;
|
||||||
private const int D3D11DeviceMethodCount = 43;
|
private const int D3D11DeviceMethodCount = 43;
|
||||||
|
|
||||||
private static SwapChainDescription CreateSwapChainDescription(IntPtr renderForm) {
|
private List<IntPtr> d3d11VTblAddresses;
|
||||||
return new SwapChainDescription {
|
private List<IntPtr> dxgiSwapChainVTblAddresses;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IntPtr Present { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IntPtr ResizeBuffers { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
|
{
|
||||||
|
if (this.d3d11VTblAddresses == null)
|
||||||
|
{
|
||||||
|
// Create temporary device + swapchain and determine method addresses
|
||||||
|
var renderForm = new Form();
|
||||||
|
|
||||||
|
Device.CreateWithSwapChain(
|
||||||
|
DriverType.Hardware,
|
||||||
|
DeviceCreationFlags.BgraSupport,
|
||||||
|
CreateSwapChainDescription(renderForm.Handle),
|
||||||
|
out var device,
|
||||||
|
out var swapChain);
|
||||||
|
|
||||||
|
if (device != null && swapChain != null)
|
||||||
|
{
|
||||||
|
this.d3d11VTblAddresses = this.GetVTblAddresses(device.NativePointer, D3D11DeviceMethodCount);
|
||||||
|
this.dxgiSwapChainVTblAddresses = this.GetVTblAddresses(swapChain.NativePointer, DxgiSwapchainMethodCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
device?.Dispose();
|
||||||
|
swapChain?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Present = this.dxgiSwapChainVTblAddresses[8];
|
||||||
|
this.ResizeBuffers = this.dxgiSwapChainVTblAddresses[13];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SwapChainDescription CreateSwapChainDescription(IntPtr renderForm)
|
||||||
|
{
|
||||||
|
return new SwapChainDescription
|
||||||
|
{
|
||||||
BufferCount = 1,
|
BufferCount = 1,
|
||||||
Flags = SwapChainFlags.None,
|
Flags = SwapChainFlags.None,
|
||||||
IsWindowed = true,
|
IsWindowed = true,
|
||||||
|
|
@ -27,74 +71,23 @@ namespace Dalamud.Game.Internal.DXGI
|
||||||
OutputHandle = renderForm,
|
OutputHandle = renderForm,
|
||||||
SampleDescription = new SampleDescription(1, 0),
|
SampleDescription = new SampleDescription(1, 0),
|
||||||
SwapEffect = SwapEffect.Discard,
|
SwapEffect = SwapEffect.Discard,
|
||||||
Usage = Usage.RenderTargetOutput
|
Usage = Usage.RenderTargetOutput,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr[] GetVTblAddresses(IntPtr pointer, int numberOfMethods)
|
private List<IntPtr> GetVTblAddresses(IntPtr pointer, int numberOfMethods)
|
||||||
{
|
{
|
||||||
return GetVTblAddresses(pointer, 0, numberOfMethods);
|
return this.GetVTblAddresses(pointer, 0, numberOfMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr[] GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods)
|
private List<IntPtr> GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods)
|
||||||
{
|
{
|
||||||
List<IntPtr> vtblAddresses = new List<IntPtr>();
|
var vtblAddresses = new List<IntPtr>();
|
||||||
IntPtr vTable = Marshal.ReadIntPtr(pointer);
|
var vTable = Marshal.ReadIntPtr(pointer);
|
||||||
for (int i = startIndex; i < startIndex + numberOfMethods; i++)
|
for (var i = startIndex; i < startIndex + numberOfMethods; i++)
|
||||||
vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes
|
vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes
|
||||||
|
|
||||||
return vtblAddresses.ToArray();
|
return vtblAddresses;
|
||||||
}
|
|
||||||
|
|
||||||
private List<IntPtr> d3d11VTblAddresses = null;
|
|
||||||
private List<IntPtr> dxgiSwapChainVTblAddresses = null;
|
|
||||||
|
|
||||||
#region Internal device resources
|
|
||||||
|
|
||||||
private Device device;
|
|
||||||
private SwapChain swapChain;
|
|
||||||
private RenderForm renderForm;
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Addresses
|
|
||||||
|
|
||||||
public IntPtr Present { get; set; }
|
|
||||||
public IntPtr ResizeBuffers { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
if (this.d3d11VTblAddresses == null) {
|
|
||||||
this.d3d11VTblAddresses = new List<IntPtr>();
|
|
||||||
this.dxgiSwapChainVTblAddresses = new List<IntPtr>();
|
|
||||||
|
|
||||||
#region Get Device and SwapChain method addresses
|
|
||||||
|
|
||||||
// Create temporary device + swapchain and determine method addresses
|
|
||||||
this.renderForm = new RenderForm();
|
|
||||||
Device.CreateWithSwapChain(
|
|
||||||
DriverType.Hardware,
|
|
||||||
DeviceCreationFlags.BgraSupport,
|
|
||||||
CreateSwapChainDescription(this.renderForm.Handle),
|
|
||||||
out this.device,
|
|
||||||
out this.swapChain
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.device != null && this.swapChain != null) {
|
|
||||||
this.d3d11VTblAddresses.AddRange(
|
|
||||||
GetVTblAddresses(this.device.NativePointer, D3D11DeviceMethodCount));
|
|
||||||
this.dxgiSwapChainVTblAddresses.AddRange(
|
|
||||||
GetVTblAddresses(this.swapChain.NativePointer, DxgiSwapchainMethodCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.device?.Dispose();
|
|
||||||
this.swapChain?.Dispose();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
Present = this.dxgiSwapChainVTblAddresses[8];
|
|
||||||
ResizeBuffers = this.dxgiSwapChainVTblAddresses[13];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,95 +3,154 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
|
||||||
using Dalamud.Game.Internal.Gui;
|
using Dalamud.Game.Internal.Gui;
|
||||||
using Dalamud.Game.Internal.Libc;
|
using Dalamud.Game.Internal.Libc;
|
||||||
using Dalamud.Game.Internal.Network;
|
using Dalamud.Game.Internal.Network;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal {
|
namespace Dalamud.Game.Internal
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Framework : IDisposable {
|
public sealed class Framework : IDisposable
|
||||||
private readonly Dalamud dalamud;
|
{
|
||||||
|
private static Stopwatch statsStopwatch = new();
|
||||||
|
|
||||||
internal bool DispatchUpdateEvents { get; set; } = true;
|
private readonly Dalamud dalamud;
|
||||||
|
private Hook<OnUpdateDetour> updateHook;
|
||||||
|
private Hook<OnDestroyDetour> destroyHook;
|
||||||
|
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Framework"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public Framework(SigScanner scanner, Dalamud dalamud)
|
||||||
|
{
|
||||||
|
this.dalamud = dalamud;
|
||||||
|
this.Address = new FrameworkAddressResolver();
|
||||||
|
this.Address.Setup(scanner);
|
||||||
|
|
||||||
|
Log.Verbose("Framework address {FrameworkAddress}", this.Address.BaseAddress);
|
||||||
|
if (this.Address.BaseAddress == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Framework is not initalized yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook virtual functions
|
||||||
|
this.HookVTable();
|
||||||
|
|
||||||
|
// Initialize subsystems
|
||||||
|
this.Libc = new LibcFunction(scanner);
|
||||||
|
|
||||||
|
this.Gui = new GameGui(this.Address.GuiManager, scanner, dalamud);
|
||||||
|
|
||||||
|
this.Network = new GameNetwork(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="OnUpdateEvent"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="framework">The Framework instance.</param>
|
||||||
|
public delegate void OnUpdateDelegate(Framework framework);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used during the native Framework::destroy.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="framework">The native Framework address.</param>
|
||||||
|
/// <returns>A value indicating if the call was successful.</returns>
|
||||||
|
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used during the native Framework::free.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The native Framework address.</returns>
|
||||||
|
public delegate IntPtr OnDestroyDelegate();
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||||
|
|
||||||
private delegate IntPtr OnDestroyDetour();
|
private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
|
||||||
|
|
||||||
public delegate void OnUpdateDelegate(Framework framework);
|
|
||||||
|
|
||||||
public delegate IntPtr OnDestroyDelegate();
|
|
||||||
|
|
||||||
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that gets fired every time the game framework updates.
|
/// Event that gets fired every time the game framework updates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnUpdateDelegate OnUpdateEvent;
|
public event OnUpdateDelegate OnUpdateEvent;
|
||||||
|
|
||||||
private Hook<OnUpdateDetour> updateHook;
|
|
||||||
|
|
||||||
private Hook<OnDestroyDetour> destroyHook;
|
|
||||||
|
|
||||||
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A raw pointer to the instance of Client::Framework
|
/// Gets or sets a value indicating whether the collection of stats is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FrameworkAddressResolver Address { get; }
|
|
||||||
|
|
||||||
#region Stats
|
|
||||||
public static bool StatsEnabled { get; set; }
|
public static bool StatsEnabled { get; set; }
|
||||||
public static Dictionary<string, List<double>> StatsHistory = new Dictionary<string, List<double>>();
|
|
||||||
private static Stopwatch statsStopwatch = new Stopwatch();
|
|
||||||
#endregion
|
|
||||||
#region Subsystems
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The GUI subsystem, used to access e.g. chat.
|
/// Gets the stats history mapping.
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, List<double>> StatsHistory = new();
|
||||||
|
|
||||||
|
#region Subsystems
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the GUI subsystem, used to access e.g. chat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameGui Gui { get; private set; }
|
public GameGui Gui { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Network subsystem, used to access network data.
|
/// Gets the Network subsystem, used to access network data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameNetwork Network { get; private set; }
|
public GameNetwork Network { get; private set; }
|
||||||
|
|
||||||
//public ResourceManager Resource { get; private set; }
|
// public ResourceManager Resource { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Libc subsystem, used to facilitate interop with std::strings.
|
||||||
|
/// </summary>
|
||||||
public LibcFunction Libc { get; private set; }
|
public LibcFunction Libc { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public Framework(SigScanner scanner, Dalamud dalamud) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
Address = new FrameworkAddressResolver();
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("Framework address {FrameworkAddress}", Address.BaseAddress);
|
|
||||||
if (Address.BaseAddress == IntPtr.Zero) {
|
|
||||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook virtual functions
|
|
||||||
HookVTable();
|
|
||||||
|
|
||||||
// Initialize subsystems
|
|
||||||
Libc = new LibcFunction(scanner);
|
|
||||||
|
|
||||||
Gui = new GameGui(Address.GuiManager, scanner, dalamud);
|
|
||||||
|
|
||||||
Network = new GameNetwork(scanner);
|
/// <summary>
|
||||||
|
/// Gets a raw pointer to the instance of Client::Framework.
|
||||||
|
/// </summary>
|
||||||
|
public FrameworkAddressResolver Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to dispatch update events.
|
||||||
|
/// </summary>
|
||||||
|
internal bool DispatchUpdateEvents { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable this module.
|
||||||
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
this.Gui.Enable();
|
||||||
|
this.Network.Enable();
|
||||||
|
|
||||||
|
this.updateHook.Enable();
|
||||||
|
this.destroyHook.Enable();
|
||||||
|
this.realDestroyHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HookVTable() {
|
/// <summary>
|
||||||
var vtable = Marshal.ReadIntPtr(Address.BaseAddress);
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Gui.Dispose();
|
||||||
|
this.Network.Dispose();
|
||||||
|
|
||||||
|
this.updateHook.Dispose();
|
||||||
|
this.destroyHook.Dispose();
|
||||||
|
this.realDestroyHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HookVTable()
|
||||||
|
{
|
||||||
|
var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress);
|
||||||
// Virtual function layout:
|
// Virtual function layout:
|
||||||
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
||||||
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
||||||
|
|
@ -100,36 +159,17 @@ namespace Dalamud.Game.Internal {
|
||||||
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
||||||
|
|
||||||
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
||||||
this.updateHook = new Hook<OnUpdateDetour>(pUpdate, new OnUpdateDetour(HandleFrameworkUpdate), this);
|
this.updateHook = new Hook<OnUpdateDetour>(pUpdate, new OnUpdateDetour(this.HandleFrameworkUpdate), this);
|
||||||
|
|
||||||
var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3);
|
var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3);
|
||||||
this.destroyHook =
|
this.destroyHook = new Hook<OnDestroyDetour>(pDestroy, new OnDestroyDelegate(this.HandleFrameworkDestroy), this);
|
||||||
new Hook<OnDestroyDetour>(pDestroy, new OnDestroyDelegate(HandleFrameworkDestroy), this);
|
|
||||||
|
|
||||||
var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2);
|
var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2);
|
||||||
this.realDestroyHook =
|
this.realDestroyHook = new Hook<OnRealDestroyDelegate>(pRealDestroy, new OnRealDestroyDelegate(this.HandleRealDestroy), this);
|
||||||
new Hook<OnRealDestroyDelegate>(pRealDestroy, new OnRealDestroyDelegate(HandleRealDestroy), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
Gui.Enable();
|
|
||||||
Network.Enable();
|
|
||||||
|
|
||||||
this.updateHook.Enable();
|
|
||||||
this.destroyHook.Enable();
|
|
||||||
this.realDestroyHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
Gui.Dispose();
|
|
||||||
Network.Dispose();
|
|
||||||
|
|
||||||
this.updateHook.Dispose();
|
|
||||||
this.destroyHook.Dispose();
|
|
||||||
this.realDestroyHook.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HandleFrameworkUpdate(IntPtr framework) {
|
private bool HandleFrameworkUpdate(IntPtr framework)
|
||||||
|
{
|
||||||
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
||||||
if (!this.dalamud.IsReady)
|
if (!this.dalamud.IsReady)
|
||||||
this.dalamud.LoadTier2();
|
this.dalamud.LoadTier2();
|
||||||
|
|
@ -137,47 +177,62 @@ namespace Dalamud.Game.Internal {
|
||||||
if (!this.dalamud.IsLoadedPluginSystem && this.dalamud.InterfaceManager.IsReady)
|
if (!this.dalamud.IsLoadedPluginSystem && this.dalamud.InterfaceManager.IsReady)
|
||||||
this.dalamud.LoadTier3();
|
this.dalamud.LoadTier3();
|
||||||
|
|
||||||
try {
|
try
|
||||||
Gui.Chat.UpdateQueue(this);
|
{
|
||||||
Gui.Toast.UpdateQueue();
|
this.Gui.Chat.UpdateQueue(this);
|
||||||
Network.UpdateQueue(this);
|
this.Gui.Toast.UpdateQueue();
|
||||||
} catch (Exception ex) {
|
this.Network.UpdateQueue(this);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.DispatchUpdateEvents)
|
if (this.DispatchUpdateEvents)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
if (StatsEnabled && OnUpdateEvent != null) {
|
{
|
||||||
|
if (StatsEnabled && this.OnUpdateEvent != null)
|
||||||
|
{
|
||||||
// Stat Tracking for Framework Updates
|
// Stat Tracking for Framework Updates
|
||||||
var invokeList = OnUpdateEvent.GetInvocationList();
|
var invokeList = this.OnUpdateEvent.GetInvocationList();
|
||||||
var notUpdated = StatsHistory.Keys.ToList();
|
var notUpdated = StatsHistory.Keys.ToList();
|
||||||
// Individually invoke OnUpdate handlers and time them.
|
// Individually invoke OnUpdate handlers and time them.
|
||||||
foreach (var d in invokeList) {
|
foreach (var d in invokeList)
|
||||||
|
{
|
||||||
statsStopwatch.Restart();
|
statsStopwatch.Restart();
|
||||||
d.Method.Invoke(d.Target, new object[]{ this });
|
d.Method.Invoke(d.Target, new object[] { this });
|
||||||
statsStopwatch.Stop();
|
statsStopwatch.Stop();
|
||||||
var key = $"{d.Target}::{d.Method.Name}";
|
var key = $"{d.Target}::{d.Method.Name}";
|
||||||
if (notUpdated.Contains(key)) notUpdated.Remove(key);
|
if (notUpdated.Contains(key)) notUpdated.Remove(key);
|
||||||
if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List<double>());
|
if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List<double>());
|
||||||
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
||||||
if (StatsHistory[key].Count > 1000) {
|
if (StatsHistory[key].Count > 1000)
|
||||||
|
{
|
||||||
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup handlers that are no longer being called
|
// Cleanup handlers that are no longer being called
|
||||||
foreach (var key in notUpdated) {
|
foreach (var key in notUpdated)
|
||||||
if (StatsHistory[key].Count > 0) {
|
{
|
||||||
|
if (StatsHistory[key].Count > 0)
|
||||||
|
{
|
||||||
StatsHistory[key].RemoveAt(0);
|
StatsHistory[key].RemoveAt(0);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
StatsHistory.Remove(key);
|
StatsHistory.Remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
OnUpdateEvent?.Invoke(this);
|
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
else
|
||||||
|
{
|
||||||
|
this.OnUpdateEvent?.Invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +254,8 @@ namespace Dalamud.Game.Internal {
|
||||||
return this.realDestroyHook.Original(framework);
|
return this.realDestroyHook.Original(framework);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr HandleFrameworkDestroy() {
|
private IntPtr HandleFrameworkDestroy()
|
||||||
|
{
|
||||||
Log.Information("Framework::Free!");
|
Log.Information("Framework::Free!");
|
||||||
|
|
||||||
// Store the pointer to the original trampoline location
|
// Store the pointer to the original trampoline location
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,43 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal {
|
namespace Dalamud.Game.Internal
|
||||||
public sealed class FrameworkAddressResolver : BaseAddressResolver {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="Framework"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the base address native Framework class.
|
||||||
|
/// </summary>
|
||||||
public IntPtr BaseAddress { get; private set; }
|
public IntPtr BaseAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address for the native GuiManager class.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GuiManager { get; private set; }
|
public IntPtr GuiManager { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address for the native ScriptManager class.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ScriptManager { get; private set; }
|
public IntPtr ScriptManager { get; private set; }
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
/// <inheritdoc/>
|
||||||
SetupFramework(sig);
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
|
{
|
||||||
|
this.SetupFramework(sig);
|
||||||
|
|
||||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
||||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
// Xiv__Framework__GetGuiManager+F 000 retn
|
||||||
GuiManager = Marshal.ReadIntPtr(BaseAddress, 0x2C08);
|
this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08);
|
||||||
|
|
||||||
// Called from Framework::Init
|
// Called from Framework::Init
|
||||||
ScriptManager = BaseAddress + 0x2C68; // note that no deref here
|
this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupFramework(SigScanner scanner) {
|
private void SetupFramework(SigScanner scanner)
|
||||||
|
{
|
||||||
// Dissasembly of part of the .dtor
|
// Dissasembly of part of the .dtor
|
||||||
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
||||||
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
|
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
|
||||||
|
|
@ -30,9 +47,9 @@ namespace Dalamud.Game.Internal {
|
||||||
var fwDtor = scanner.ScanText("48C705????????00000000 E8???????? 488D??????0000 E8???????? 488D");
|
var fwDtor = scanner.ScanText("48C705????????00000000 E8???????? 488D??????0000 E8???????? 488D");
|
||||||
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
|
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
|
||||||
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
||||||
|
|
||||||
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
||||||
BaseAddress = Marshal.ReadIntPtr(pFramework);
|
this.BaseAddress = Marshal.ReadIntPtr(pFramework);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,66 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui.Addon {
|
namespace Dalamud.Game.Internal.Gui.Addon
|
||||||
public class Addon {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an in-game UI "Addon".
|
||||||
|
/// </summary>
|
||||||
|
public class Addon
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The address of the addon.
|
||||||
|
/// </summary>
|
||||||
public IntPtr Address;
|
public IntPtr Address;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The addon interop data.
|
||||||
|
/// </summary>
|
||||||
protected Structs.Addon addonStruct;
|
protected Structs.Addon addonStruct;
|
||||||
|
|
||||||
public Addon(IntPtr address, Structs.Addon addonStruct) {
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Addon"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The address of the addon.</param>
|
||||||
|
/// <param name="addonStruct">The addon interop data.</param>
|
||||||
|
public Addon(IntPtr address, Structs.Addon addonStruct)
|
||||||
|
{
|
||||||
this.Address = address;
|
this.Address = address;
|
||||||
this.addonStruct = addonStruct;
|
this.addonStruct = addonStruct;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the addon.
|
||||||
|
/// </summary>
|
||||||
public string Name => this.addonStruct.Name;
|
public string Name => this.addonStruct.Name;
|
||||||
public short X => this.addonStruct.X;
|
|
||||||
public short Y => this.addonStruct.Y;
|
|
||||||
public float Scale => this.addonStruct.Scale;
|
|
||||||
public unsafe float Width => this.addonStruct.RootNode->Width * Scale;
|
|
||||||
public unsafe float Height => this.addonStruct.RootNode->Height * Scale;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the X position of the addon on screen.
|
||||||
|
/// </summary>
|
||||||
|
public short X => this.addonStruct.X;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Y position of the addon on screen.
|
||||||
|
/// </summary>
|
||||||
|
public short Y => this.addonStruct.Y;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the scale of the addon.
|
||||||
|
/// </summary>
|
||||||
|
public float Scale => this.addonStruct.Scale;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the width of the addon. This may include non-visible parts.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe float Width => this.addonStruct.RootNode->Width * this.Scale;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the height of the addon. This may include non-visible parts.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe float Height => this.addonStruct.RootNode->Height * this.Scale;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the addon is visible.
|
||||||
|
/// </summary>
|
||||||
public bool Visible => (this.addonStruct.Flags & 0x20) == 0x20;
|
public bool Visible => (this.addonStruct.Flags & 0x20) == 0x20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,35 +3,127 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using Dalamud.Game.Internal.Libc;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
public sealed class ChatGui : IDisposable {
|
{
|
||||||
private readonly Queue<XivChatEntry> chatQueue = new Queue<XivChatEntry>();
|
/// <summary>
|
||||||
|
/// This class handles interacting with the native chat UI.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ChatGui : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Dalamud dalamud;
|
||||||
|
private readonly ChatGuiAddressResolver address;
|
||||||
|
|
||||||
#region Events
|
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||||
|
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||||
|
|
||||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||||
public delegate void OnMessageRawDelegate(XivChatType type, uint senderId, ref StdString sender, ref StdString message, ref bool isHandled);
|
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
|
||||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
private IntPtr baseAddress = IntPtr.Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
/// Initializes a new instance of the <see cref="ChatGui"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnCheckMessageHandledDelegate OnCheckMessageHandled;
|
/// <param name="baseAddress">The base address of the ChatManager.</param>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public ChatGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud)
|
||||||
|
{
|
||||||
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
|
this.address = new ChatGuiAddressResolver(baseAddress);
|
||||||
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
|
Log.Verbose("Chat manager address {ChatManager}", this.address.BaseAddress);
|
||||||
|
|
||||||
|
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, new PrintMessageDelegate(this.HandlePrintMessageDetour), this);
|
||||||
|
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, new PopulateItemLinkDelegate(this.HandlePopulateItemLinkDetour), this);
|
||||||
|
this.interactableLinkClickedHook = new Hook<InteractableLinkClickedDelegate>(this.address.InteractableLinkClicked, new InteractableLinkClickedDelegate(this.InteractableLinkClickedDetour));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="OnChatMessage"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of chat.</param>
|
||||||
|
/// <param name="senderId">The sender ID.</param>
|
||||||
|
/// <param name="sender">The sender name.</param>
|
||||||
|
/// <param name="message">The message sent.</param>
|
||||||
|
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||||
|
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="OnChatMessageRaw"/> 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>
|
||||||
|
[Obsolete("Please use OnMessageDelegate instead. For modifications, it will take precedence.")]
|
||||||
|
public delegate void OnMessageRawDelegate(XivChatType type, uint senderId, ref StdString sender, ref StdString message, ref bool isHandled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="OnCheckMessageHandled"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of chat.</param>
|
||||||
|
/// <param name="senderId">The sender ID.</param>
|
||||||
|
/// <param name="sender">The sender name.</param>
|
||||||
|
/// <param name="message">The message sent.</param>
|
||||||
|
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||||
|
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="OnChatMessageHandled"/> 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="OnChatMessageUnhandled"/> 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>
|
/// <summary>
|
||||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnMessageDelegate OnChatMessage;
|
public event OnMessageDelegate OnChatMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that will be fired when a chat message is sent by the game, containing raw, unparsed data.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Please use OnChatMessage instead. For modifications, it will take precedence.")]
|
||||||
|
public event OnMessageRawDelegate OnChatMessageRaw;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
||||||
|
/// </summary>
|
||||||
|
public event OnCheckMessageHandledDelegate OnCheckMessageHandled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -43,101 +135,239 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
public event OnMessageUnhandledDelegate OnChatMessageUnhandled;
|
public event OnMessageUnhandledDelegate OnChatMessageUnhandled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that will be fired when a chat message is sent by the game, containing raw, unparsed data.
|
/// Gets the ID of the last linked item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Please use OnChatMessage instead. For modifications, it will take precedence.")]
|
|
||||||
public event OnMessageRawDelegate OnChatMessageRaw;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Hooks
|
|
||||||
|
|
||||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
|
||||||
|
|
||||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
|
||||||
|
|
||||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Delegates
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName,
|
|
||||||
IntPtr message,
|
|
||||||
uint senderId, IntPtr parameter);
|
|
||||||
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
|
||||||
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public int LastLinkedItemId { get; private set; }
|
public int LastLinkedItemId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the flags of the last linked item.
|
||||||
|
/// </summary>
|
||||||
public byte LastLinkedItemFlags { get; private set; }
|
public byte LastLinkedItemFlags { get; private set; }
|
||||||
|
|
||||||
private ChatGuiAddressResolver Address { get; }
|
/// <summary>
|
||||||
|
/// Enables this module.
|
||||||
private IntPtr baseAddress = IntPtr.Zero;
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
private readonly Dalamud dalamud;
|
{
|
||||||
|
|
||||||
public ChatGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
|
|
||||||
Address = new ChatGuiAddressResolver(baseAddress);
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("Chat manager address {ChatManager}", Address.BaseAddress);
|
|
||||||
|
|
||||||
this.printMessageHook =
|
|
||||||
new Hook<PrintMessageDelegate>(Address.PrintMessage, new PrintMessageDelegate(HandlePrintMessageDetour),
|
|
||||||
this);
|
|
||||||
this.populateItemLinkHook =
|
|
||||||
new Hook<PopulateItemLinkDelegate>(Address.PopulateItemLinkObject,
|
|
||||||
new PopulateItemLinkDelegate(HandlePopulateItemLinkDetour),
|
|
||||||
this);
|
|
||||||
this.interactableLinkClickedHook =
|
|
||||||
new Hook<InteractableLinkClickedDelegate>(Address.InteractableLinkClicked,
|
|
||||||
new InteractableLinkClickedDelegate(InteractableLinkClickedDetour));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.printMessageHook.Enable();
|
this.printMessageHook.Enable();
|
||||||
this.populateItemLinkHook.Enable();
|
this.populateItemLinkHook.Enable();
|
||||||
this.interactableLinkClickedHook.Enable();
|
this.interactableLinkClickedHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
this.printMessageHook.Dispose();
|
this.printMessageHook.Dispose();
|
||||||
this.populateItemLinkHook.Dispose();
|
this.populateItemLinkHook.Dispose();
|
||||||
this.interactableLinkClickedHook.Dispose();
|
this.interactableLinkClickedHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) {
|
/// <summary>
|
||||||
try {
|
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||||
|
/// later to be processed when UpdateQueue() is called.
|
||||||
|
/// </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)
|
||||||
|
{
|
||||||
|
Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||||
|
this.PrintChat(new XivChatEntry
|
||||||
|
{
|
||||||
|
MessageBytes = Encoding.UTF8.GetBytes(message),
|
||||||
|
Type = this.dalamud.Configuration.GeneralChatType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||||
|
/// later to be processed when UpdateQueue() is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message to send.</param>
|
||||||
|
public void Print(SeString message)
|
||||||
|
{
|
||||||
|
Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||||
|
this.PrintChat(new XivChatEntry
|
||||||
|
{
|
||||||
|
MessageBytes = message.Encode(),
|
||||||
|
Type = this.dalamud.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
|
||||||
|
{
|
||||||
|
MessageBytes = Encoding.UTF8.GetBytes(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
|
||||||
|
{
|
||||||
|
MessageBytes = message.Encode(),
|
||||||
|
Type = XivChatType.Urgent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process a chat queue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="framework">The Framework instance.</param>
|
||||||
|
public void UpdateQueue(Framework framework)
|
||||||
|
{
|
||||||
|
while (this.chatQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var chat = this.chatQueue.Dequeue();
|
||||||
|
|
||||||
|
if (this.baseAddress == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderRaw = Encoding.UTF8.GetBytes(chat.Name ?? string.Empty);
|
||||||
|
using var senderOwned = framework.Libc.NewString(senderRaw);
|
||||||
|
|
||||||
|
var messageRaw = chat.MessageBytes ?? new byte[0];
|
||||||
|
using var messageOwned = framework.Libc.NewString(messageRaw);
|
||||||
|
|
||||||
|
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||||
|
|
||||||
LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||||
LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||||
|
|
||||||
Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{LastLinkedItemId}");
|
Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage,
|
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||||
uint senderid, IntPtr parameter) {
|
{
|
||||||
var retVal = IntPtr.Zero;
|
var retVal = IntPtr.Zero;
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
var sender = StdString.ReadFromPointer(pSenderName);
|
var sender = StdString.ReadFromPointer(pSenderName);
|
||||||
var message = StdString.ReadFromPointer(pMessage);
|
var message = StdString.ReadFromPointer(pMessage);
|
||||||
|
|
||||||
|
|
@ -146,32 +376,35 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
|
|
||||||
Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
||||||
|
|
||||||
//Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||||
|
|
||||||
var originalMessageData = (byte[]) message.RawData.Clone();
|
var originalMessageData = (byte[])message.RawData.Clone();
|
||||||
var oldEdited = parsedMessage.Encode();
|
var oldEdited = parsedMessage.Encode();
|
||||||
|
|
||||||
// Call events
|
// Call events
|
||||||
var isHandled = false;
|
var isHandled = false;
|
||||||
OnCheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
this.OnCheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||||
|
|
||||||
if (!isHandled) {
|
if (!isHandled)
|
||||||
OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
{
|
||||||
OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled);
|
this.OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||||
|
this.OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newEdited = parsedMessage.Encode();
|
var newEdited = parsedMessage.Encode();
|
||||||
|
|
||||||
if (!FastByteArrayCompare(oldEdited, newEdited)) {
|
if (!FastByteArrayCompare(oldEdited, newEdited))
|
||||||
|
{
|
||||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||||
message.RawData = newEdited;
|
message.RawData = newEdited;
|
||||||
Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var messagePtr = pMessage;
|
var messagePtr = pMessage;
|
||||||
OwnedStdString allocatedString = null;
|
OwnedStdString allocatedString = null;
|
||||||
|
|
||||||
if (!FastByteArrayCompare(originalMessageData, message.RawData)) {
|
if (!FastByteArrayCompare(originalMessageData, message.RawData))
|
||||||
|
{
|
||||||
allocatedString = this.dalamud.Framework.Libc.NewString(message.RawData);
|
allocatedString = this.dalamud.Framework.Libc.NewString(message.RawData);
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
$"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
$"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||||
|
|
@ -181,19 +414,21 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
// Print the original chat if it's handled.
|
// Print the original chat if it's handled.
|
||||||
if (isHandled)
|
if (isHandled)
|
||||||
{
|
{
|
||||||
OnChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
this.OnChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter);
|
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter);
|
||||||
OnChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
this.OnChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.baseAddress == IntPtr.Zero)
|
if (this.baseAddress == IntPtr.Zero)
|
||||||
this.baseAddress = manager;
|
this.baseAddress = manager;
|
||||||
|
|
||||||
allocatedString?.Dispose();
|
allocatedString?.Dispose();
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||||
}
|
}
|
||||||
|
|
@ -201,47 +436,14 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<(string pluginName, uint commandId), Action<uint, SeString>> dalamudLinkHandlers = new Dictionary<(string, uint), Action<uint, SeString>>();
|
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||||
|
{
|
||||||
/// <summary>
|
try
|
||||||
/// Create a link handler
|
{
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName"></param>
|
|
||||||
/// <param name="commandId"></param>
|
|
||||||
/// <param name="commandAction"></param>
|
|
||||||
/// <returns></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 a registered link handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName"></param>
|
|
||||||
/// <param name="commandId"></param>
|
|
||||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId) {
|
|
||||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId))) {
|
|
||||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove all handlers owned by a plugin.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName"></param>
|
|
||||||
internal void RemoveChatLinkHandler(string pluginName) {
|
|
||||||
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.pluginName == pluginName)) {
|
|
||||||
this.dalamudLinkHandlers.Remove(handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) {
|
|
||||||
try {
|
|
||||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||||
|
|
||||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink) {
|
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||||
|
{
|
||||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -258,100 +460,22 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||||
if (payloads.Count == 0) return;
|
if (payloads.Count == 0) return;
|
||||||
var linkPayload = payloads[0];
|
var linkPayload = payloads[0];
|
||||||
if (linkPayload is DalamudLinkPayload link) {
|
if (linkPayload is DalamudLinkPayload link)
|
||||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) {
|
{
|
||||||
|
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||||
|
{
|
||||||
Log.Verbose($"Sending DalamudLink to {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));
|
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
|
||||||
// Copyright (c) 2008-2013 Hafthor Stefansson
|
|
||||||
// Distributed under the MIT/X11 software license
|
|
||||||
// Ref: http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
// https://stackoverflow.com/a/8808245
|
|
||||||
static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2)
|
|
||||||
{
|
|
||||||
if (a1 == a2) return true;
|
|
||||||
if (a1 == null || a2 == null || a1.Length != a2.Length)
|
|
||||||
return false;
|
|
||||||
fixed (byte* p1 = a1, p2 = a2)
|
|
||||||
{
|
{
|
||||||
byte* x1 = p1, x2 = p2;
|
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||||
int l = a1.Length;
|
|
||||||
for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
|
|
||||||
if (*((long*)x1) != *((long*)x2)) return false;
|
|
||||||
if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; }
|
|
||||||
if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; }
|
|
||||||
if ((l & 1) != 0) if (*((byte*)x1) != *((byte*)x2)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Print(string message) {
|
|
||||||
Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
|
||||||
PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = Encoding.UTF8.GetBytes(message),
|
|
||||||
Type = this.dalamud.Configuration.GeneralChatType
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Print(SeString message) {
|
|
||||||
Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
|
||||||
PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = message.Encode(),
|
|
||||||
Type = this.dalamud.Configuration.GeneralChatType
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PrintError(string message) {
|
|
||||||
Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
|
||||||
PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = Encoding.UTF8.GetBytes(message),
|
|
||||||
Type = XivChatType.Urgent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PrintError(SeString message) {
|
|
||||||
Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
|
||||||
PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = message.Encode(),
|
|
||||||
Type = XivChatType.Urgent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process a chat queue.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateQueue(Framework framework) {
|
|
||||||
while (this.chatQueue.Count > 0) {
|
|
||||||
var chat = this.chatQueue.Dequeue();
|
|
||||||
|
|
||||||
if (this.baseAddress == IntPtr.Zero) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var senderRaw = Encoding.UTF8.GetBytes(chat.Name ?? "");
|
|
||||||
using var senderOwned = framework.Libc.NewString(senderRaw);
|
|
||||||
|
|
||||||
var messageRaw = chat.MessageBytes ?? new byte[0];
|
|
||||||
using var messageOwned = framework.Libc.NewString(messageRaw);
|
|
||||||
|
|
||||||
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,118 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver {
|
{
|
||||||
public IntPtr BaseAddress { get; }
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||||
public IntPtr PrintMessage { get; private set; }
|
/// </summary>
|
||||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||||
public IntPtr InteractableLinkClicked { get; private set; }
|
{
|
||||||
|
/// <summary>
|
||||||
public ChatGuiAddressResolver(IntPtr baseAddres) {
|
/// Initializes a new instance of the <see cref="ChatGuiAddressResolver"/> class.
|
||||||
BaseAddress = baseAddres;
|
/// </summary>
|
||||||
|
/// <param name="baseAddres">The base address of the native ChatManager class.</param>
|
||||||
|
public ChatGuiAddressResolver(IntPtr baseAddres)
|
||||||
|
{
|
||||||
|
this.BaseAddress = baseAddres;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the base address of the native ChatManager class.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr BaseAddress { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native PrintMessage method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr PrintMessage { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native PopulateItemLinkObject method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native InteractableLinkClicked method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr InteractableLinkClicked { get; private set; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
--- 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
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
//PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
|
||||||
PrintMessage =
|
|
||||||
sig.ScanText(
|
|
||||||
"4055 53 56 4154 4157 48 8d ac 24 ?? ?? ?? ?? 48 81 ec 20 02 00 00 48 8b 05"
|
|
||||||
);
|
|
||||||
//PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
|
||||||
|
|
||||||
//PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
/// <inheritdoc/>
|
||||||
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
|
{
|
||||||
|
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
||||||
|
this.PrintMessage = sig.ScanText("4055 53 56 4154 4157 48 8d ac 24 ?? ?? ?? ?? 48 81 ec 20 02 00 00 48 8b 05");
|
||||||
|
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||||
|
|
||||||
//PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||||
|
|
||||||
//PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 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 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
|
||||||
|
|
||||||
InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||||
|
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||||
|
|
||||||
|
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,292 +1,218 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SharpDX;
|
using SharpDX;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class handling many aspects of the in-game UI.
|
||||||
|
/// </summary>
|
||||||
public sealed class GameGui : IDisposable
|
public sealed class GameGui : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Dalamud dalamud;
|
/// <summary>
|
||||||
|
/// The delegate of the native method that gets the Client::UI::UIModule address.
|
||||||
private GameGuiAddressResolver Address { get; }
|
/// </summary>
|
||||||
|
/// <returns>The Client::UI::UIModule address.</returns>
|
||||||
public ChatGui Chat { get; private set; }
|
|
||||||
public PartyFinderGui PartyFinder { get; private set; }
|
|
||||||
public ToastGui Toast { get; private set; }
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr SetGlobalBgmDelegate(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6);
|
|
||||||
private readonly Hook<SetGlobalBgmDelegate> setGlobalBgmHook;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
|
||||||
private readonly Hook<HandleItemHoverDelegate> handleItemHoverHook;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
|
||||||
private readonly Hook<HandleItemOutDelegate> handleItemOutHook;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5);
|
|
||||||
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
|
|
||||||
private Hook<HandleActionOutDelegate> handleActionOutHook;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
||||||
private delegate IntPtr GetUIObjectDelegate();
|
|
||||||
private readonly GetUIObjectDelegate getUIObject;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr GetUIMapObjectDelegate(IntPtr UIObject);
|
|
||||||
private GetUIMapObjectDelegate getUIMapObject;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
|
|
||||||
private delegate bool OpenMapWithFlagDelegate(IntPtr UIMapObject, string flag);
|
|
||||||
private OpenMapWithFlagDelegate openMapWithFlag;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
||||||
internal delegate IntPtr GetMatrixSingletonDelegate();
|
|
||||||
internal readonly GetMatrixSingletonDelegate getMatrixSingleton;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private unsafe delegate bool ScreenToWorldNativeDelegate(
|
|
||||||
float *camPos, float *clipPos, float rayDistance, float *worldPos, int *unknown);
|
|
||||||
private readonly ScreenToWorldNativeDelegate screenToWorldNative;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
|
|
||||||
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
|
||||||
|
|
||||||
// Return a Client::UI::UIModule
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
||||||
public delegate IntPtr GetBaseUIObjectDelegate();
|
|
||||||
public readonly GetBaseUIObjectDelegate GetBaseUIObject;
|
public readonly GetBaseUIObjectDelegate GetBaseUIObject;
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
|
private readonly Dalamud dalamud;
|
||||||
private delegate IntPtr GetUIObjectByNameDelegate(IntPtr thisPtr, string uiName, int index);
|
private readonly GameGuiAddressResolver address;
|
||||||
|
|
||||||
|
private readonly GetMatrixSingletonDelegate getMatrixSingleton;
|
||||||
|
private readonly GetUIObjectDelegate getUIObject;
|
||||||
|
private readonly ScreenToWorldNativeDelegate screenToWorldNative;
|
||||||
private readonly GetUIObjectByNameDelegate getUIObjectByName;
|
private readonly GetUIObjectByNameDelegate getUIObjectByName;
|
||||||
|
|
||||||
private delegate IntPtr GetUiModuleDelegate(IntPtr basePtr);
|
|
||||||
private readonly GetUiModuleDelegate getUiModule;
|
private readonly GetUiModuleDelegate getUiModule;
|
||||||
|
private readonly GetAgentModuleDelegate getAgentModule;
|
||||||
|
|
||||||
private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule);
|
private readonly Hook<SetGlobalBgmDelegate> setGlobalBgmHook;
|
||||||
private GetAgentModuleDelegate getAgentModule;
|
private readonly Hook<HandleItemHoverDelegate> handleItemHoverHook;
|
||||||
|
private readonly Hook<HandleItemOutDelegate> handleItemOutHook;
|
||||||
|
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook;
|
||||||
|
private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
|
||||||
|
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
||||||
|
|
||||||
public bool GameUiHidden { get; private set; }
|
private GetUIMapObjectDelegate getUIMapObject;
|
||||||
|
private OpenMapWithFlagDelegate openMapWithFlag;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event which is fired when the game UI hiding is toggled.
|
/// Initializes a new instance of the <see cref="GameGui"/> class.
|
||||||
|
/// This class is responsible for many aspects of interacting with the native game UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<bool> OnUiHideToggled;
|
/// <param name="baseAddress">The base address of the native GuiManager class.</param>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
/// <summary>
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
/// The item ID that is currently hovered by the player. 0 when no item is hovered.
|
|
||||||
/// If > 1.000.000, subtract 1.000.000 and treat it as HQ
|
|
||||||
/// </summary>
|
|
||||||
public ulong HoveredItem { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The action ID that is current hovered by the player. 0 when no action is hovered.
|
|
||||||
/// </summary>
|
|
||||||
public HoveredAction HoveredAction { get; } = new HoveredAction();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that is fired when the currently hovered item changes.
|
|
||||||
/// </summary>
|
|
||||||
public EventHandler<ulong> HoveredItemChanged { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that is fired when the currently hovered action changes.
|
|
||||||
/// </summary>
|
|
||||||
public EventHandler<HoveredAction> HoveredActionChanged { get; set; }
|
|
||||||
|
|
||||||
public GameGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud)
|
public GameGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud)
|
||||||
{
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
Address = new GameGuiAddressResolver(baseAddress);
|
this.address = new GameGuiAddressResolver(baseAddress);
|
||||||
Address.Setup(scanner);
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
Log.Verbose("===== G A M E G U I =====");
|
Log.Verbose("===== G A M E G U I =====");
|
||||||
|
|
||||||
Log.Verbose("GameGuiManager address {Address}", Address.BaseAddress);
|
Log.Verbose("GameGuiManager address {Address:X}", this.address.BaseAddress.ToInt64());
|
||||||
Log.Verbose("SetGlobalBgm address {Address}", Address.SetGlobalBgm);
|
Log.Verbose("SetGlobalBgm address {Address:X}", this.address.SetGlobalBgm.ToInt64());
|
||||||
Log.Verbose("HandleItemHover address {Address}", Address.HandleItemHover);
|
Log.Verbose("HandleItemHover address {Address:X}", this.address.HandleItemHover.ToInt64());
|
||||||
Log.Verbose("HandleItemOut address {Address}", Address.HandleItemOut);
|
Log.Verbose("HandleItemOut address {Address:X}", this.address.HandleItemOut.ToInt64());
|
||||||
Log.Verbose("GetUIObject address {Address}", Address.GetUIObject);
|
Log.Verbose("GetUIObject address {Address:X}", this.address.GetUIObject.ToInt64());
|
||||||
Log.Verbose("GetAgentModule address {Address}", Address.GetAgentModule);
|
Log.Verbose("GetAgentModule address {Address:X}", this.address.GetAgentModule.ToInt64());
|
||||||
|
|
||||||
Chat = new ChatGui(Address.ChatManager, scanner, dalamud);
|
this.Chat = new ChatGui(this.address.ChatManager, scanner, dalamud);
|
||||||
PartyFinder = new PartyFinderGui(scanner, dalamud);
|
this.PartyFinder = new PartyFinderGui(scanner, dalamud);
|
||||||
Toast = new ToastGui(scanner, dalamud);
|
this.Toast = new ToastGui(scanner, dalamud);
|
||||||
|
|
||||||
this.setGlobalBgmHook =
|
this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, new SetGlobalBgmDelegate(this.HandleSetGlobalBgmDetour), this);
|
||||||
new Hook<SetGlobalBgmDelegate>(Address.SetGlobalBgm,
|
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, new HandleItemHoverDelegate(this.HandleItemHoverDetour), this);
|
||||||
new SetGlobalBgmDelegate(HandleSetGlobalBgmDetour),
|
|
||||||
this);
|
|
||||||
this.handleItemHoverHook =
|
|
||||||
new Hook<HandleItemHoverDelegate>(Address.HandleItemHover,
|
|
||||||
new HandleItemHoverDelegate(HandleItemHoverDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
this.handleItemOutHook =
|
this.handleItemOutHook = new Hook<HandleItemOutDelegate>(this.address.HandleItemOut, new HandleItemOutDelegate(this.HandleItemOutDetour), this);
|
||||||
new Hook<HandleItemOutDelegate>(Address.HandleItemOut,
|
|
||||||
new HandleItemOutDelegate(HandleItemOutDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
this.handleActionHoverHook =
|
this.handleActionHoverHook = new Hook<HandleActionHoverDelegate>(this.address.HandleActionHover, new HandleActionHoverDelegate(this.HandleActionHoverDetour), this);
|
||||||
new Hook<HandleActionHoverDelegate>(Address.HandleActionHover,
|
this.handleActionOutHook = new Hook<HandleActionOutDelegate>(this.address.HandleActionOut, new HandleActionOutDelegate(this.HandleActionOutDetour), this);
|
||||||
new HandleActionHoverDelegate(HandleActionHoverDetour),
|
|
||||||
this);
|
|
||||||
this.handleActionOutHook =
|
|
||||||
new Hook<HandleActionOutDelegate>(Address.HandleActionOut,
|
|
||||||
new HandleActionOutDelegate(HandleActionOutDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(Address.GetUIObject);
|
|
||||||
|
|
||||||
this.getMatrixSingleton =
|
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(this.address.GetUIObject);
|
||||||
Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(Address.GetMatrixSingleton);
|
|
||||||
|
|
||||||
this.screenToWorldNative =
|
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton);
|
||||||
Marshal.GetDelegateForFunctionPointer<ScreenToWorldNativeDelegate>(Address.ScreenToWorld);
|
|
||||||
|
|
||||||
this.toggleUiHideHook = new Hook<ToggleUiHideDelegate>(Address.ToggleUiHide, new ToggleUiHideDelegate(ToggleUiHideDetour), this);
|
this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer<ScreenToWorldNativeDelegate>(this.address.ScreenToWorld);
|
||||||
|
|
||||||
this.GetBaseUIObject = Marshal.GetDelegateForFunctionPointer<GetBaseUIObjectDelegate>(Address.GetBaseUIObject);
|
this.toggleUiHideHook = new Hook<ToggleUiHideDelegate>(this.address.ToggleUiHide, new ToggleUiHideDelegate(this.ToggleUiHideDetour), this);
|
||||||
this.getUIObjectByName = Marshal.GetDelegateForFunctionPointer<GetUIObjectByNameDelegate>(Address.GetUIObjectByName);
|
|
||||||
|
|
||||||
this.getUiModule = Marshal.GetDelegateForFunctionPointer<GetUiModuleDelegate>(Address.GetUIModule);
|
this.GetBaseUIObject = Marshal.GetDelegateForFunctionPointer<GetBaseUIObjectDelegate>(this.address.GetBaseUIObject);
|
||||||
this.getAgentModule = Marshal.GetDelegateForFunctionPointer<GetAgentModuleDelegate>(Address.GetAgentModule);
|
this.getUIObjectByName = Marshal.GetDelegateForFunctionPointer<GetUIObjectByNameDelegate>(this.address.GetUIObjectByName);
|
||||||
|
|
||||||
|
this.getUiModule = Marshal.GetDelegateForFunctionPointer<GetUiModuleDelegate>(this.address.GetUIModule);
|
||||||
|
this.getAgentModule = Marshal.GetDelegateForFunctionPointer<GetAgentModuleDelegate>(this.address.GetAgentModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr HandleSetGlobalBgmDetour(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6) {
|
// Marshaled delegates
|
||||||
var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
|
|
||||||
|
|
||||||
Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal);
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) {
|
|
||||||
var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
|
|
||||||
|
|
||||||
if (retVal.ToInt64() == 22) {
|
|
||||||
var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
|
|
||||||
this.HoveredItem = itemId;
|
|
||||||
|
|
||||||
try {
|
|
||||||
HoveredItemChanged?.Invoke(this, itemId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
|
||||||
{
|
|
||||||
var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
|
|
||||||
|
|
||||||
if (a3 != IntPtr.Zero && a4 == 1) {
|
|
||||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
|
||||||
|
|
||||||
if (a3Val == 255) {
|
|
||||||
this.HoveredItem = 0ul;
|
|
||||||
|
|
||||||
try {
|
|
||||||
HoveredItemChanged?.Invoke(this, 0ul);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Verbose("HoverItemId: 0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
|
|
||||||
{
|
|
||||||
handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
|
|
||||||
HoveredAction.ActionKind = actionKind;
|
|
||||||
HoveredAction.BaseActionID = actionId;
|
|
||||||
HoveredAction.ActionID = (uint) Marshal.ReadInt32(hoverState, 0x3C);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
|
||||||
}
|
|
||||||
Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
|
|
||||||
{
|
|
||||||
var retVal = handleActionOutHook.Original(agentActionDetail, a2, a3, a4);
|
|
||||||
|
|
||||||
if (a3 != IntPtr.Zero && a4 == 1)
|
|
||||||
{
|
|
||||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
|
||||||
|
|
||||||
if (a3Val == 255)
|
|
||||||
{
|
|
||||||
this.HoveredAction.ActionKind = HoverActionKind.None;
|
|
||||||
HoveredAction.BaseActionID = 0;
|
|
||||||
HoveredAction.ActionID = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Could not dispatch HoveredActionChanged event.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Verbose("HoverActionId: 0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the in-game map with a flag on the location of the parameter
|
/// The delegate type of the native method that gets the Client::UI::UIModule address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mapLink">Link to the map to be opened</param>
|
/// <returns>The Client::UI::UIModule address.</returns>
|
||||||
/// <returns>True if there were no errors and it could open the map</returns>
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
public bool OpenMapWithMapLink(MapLinkPayload mapLink) {
|
public delegate IntPtr GetBaseUIObjectDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate IntPtr GetMatrixSingletonDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate IntPtr GetUIObjectDelegate();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private unsafe delegate bool ScreenToWorldNativeDelegate(float* camPos, float* clipPos, float rayDistance, float* worldPos, int* unknown);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
|
||||||
|
private delegate IntPtr GetUIObjectByNameDelegate(IntPtr thisPtr, string uiName, int index);
|
||||||
|
|
||||||
|
private delegate IntPtr GetUiModuleDelegate(IntPtr basePtr);
|
||||||
|
|
||||||
|
private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule);
|
||||||
|
|
||||||
|
// Hooked delegates
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
|
||||||
|
private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event which is fired when the game UI hiding is toggled.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<bool> OnUiHideToggled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="Chat"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public ChatGui Chat { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="PartyFinder"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public PartyFinderGui PartyFinder { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="Toast"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public ToastGui Toast { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the game UI is hidden.
|
||||||
|
/// </summary>
|
||||||
|
public bool GameUiHidden { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item ID that is currently hovered by the player. 0 when no item is hovered.
|
||||||
|
/// If > 1.000.000, subtract 1.000.000 and treat it as HQ.
|
||||||
|
/// </summary>
|
||||||
|
public ulong HoveredItem { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the action ID that is current hovered by the player. 0 when no action is hovered.
|
||||||
|
/// </summary>
|
||||||
|
public HoveredAction HoveredAction { get; } = new HoveredAction();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the event that is fired when the currently hovered item changes.
|
||||||
|
/// </summary>
|
||||||
|
public EventHandler<ulong> HoveredItemChanged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the event that is fired when the currently hovered action changes.
|
||||||
|
/// </summary>
|
||||||
|
public EventHandler<HoveredAction> HoveredActionChanged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the in-game map with a flag on the location of the parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mapLink">Link to the map to be opened.</param>
|
||||||
|
/// <returns>True if there were no errors and it could open the map.</returns>
|
||||||
|
public bool OpenMapWithMapLink(MapLinkPayload mapLink)
|
||||||
|
{
|
||||||
var uiObjectPtr = this.getUIObject();
|
var uiObjectPtr = this.getUIObject();
|
||||||
|
|
||||||
if (uiObjectPtr.Equals(IntPtr.Zero)) {
|
if (uiObjectPtr.Equals(IntPtr.Zero))
|
||||||
|
{
|
||||||
Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()");
|
Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getUIMapObject =
|
this.getUIMapObject = this.address.GetVirtualFunction<GetUIMapObjectDelegate>(uiObjectPtr, 0, 8);
|
||||||
Address.GetVirtualFunction<GetUIMapObjectDelegate>(uiObjectPtr, 0, 8);
|
|
||||||
|
|
||||||
|
|
||||||
var uiMapObjectPtr = this.getUIMapObject(uiObjectPtr);
|
var uiMapObjectPtr = this.getUIMapObject(uiObjectPtr);
|
||||||
|
|
||||||
if (uiMapObjectPtr.Equals(IntPtr.Zero)) {
|
if (uiMapObjectPtr.Equals(IntPtr.Zero))
|
||||||
|
{
|
||||||
Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()");
|
Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openMapWithFlag =
|
this.openMapWithFlag = this.address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
||||||
Address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
|
||||||
|
|
||||||
var mapLinkString = mapLink.DataString;
|
var mapLinkString = mapLink.DataString;
|
||||||
|
|
||||||
|
|
@ -298,35 +224,36 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts in-world coordinates to screen coordinates (upper left corner origin).
|
/// Converts in-world coordinates to screen coordinates (upper left corner origin).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="worldPos">Coordinates in the world</param>
|
/// <param name="worldPos">Coordinates in the world.</param>
|
||||||
/// <param name="screenPos">Converted coordinates</param>
|
/// <param name="screenPos">Converted coordinates.</param>
|
||||||
/// <returns>True if worldPos corresponds to a position in front of the camera</returns>
|
/// <returns>True if worldPos corresponds to a position in front of the camera.</returns>
|
||||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
|
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
|
||||||
{
|
{
|
||||||
// Get base object with matrices
|
// Get base object with matrices
|
||||||
var matrixSingleton = this.getMatrixSingleton();
|
var matrixSingleton = this.getMatrixSingleton();
|
||||||
|
|
||||||
// Read current ViewProjectionMatrix plus game window size
|
// Read current ViewProjectionMatrix plus game window size
|
||||||
var viewProjectionMatrix = new Matrix();
|
var viewProjectionMatrix = default(Matrix);
|
||||||
float width, height;
|
float width, height;
|
||||||
var windowPos = ImGuiHelpers.MainViewport.Pos;
|
var windowPos = ImGuiHelpers.MainViewport.Pos;
|
||||||
|
|
||||||
unsafe {
|
unsafe
|
||||||
var rawMatrix = (float*) (matrixSingleton + 0x1b4).ToPointer();
|
{
|
||||||
|
var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer();
|
||||||
|
|
||||||
for (var i = 0; i < 16; i++, rawMatrix++)
|
for (var i = 0; i < 16; i++, rawMatrix++)
|
||||||
viewProjectionMatrix[i] = *rawMatrix;
|
viewProjectionMatrix[i] = *rawMatrix;
|
||||||
|
|
||||||
width = *rawMatrix;
|
width = *rawMatrix;
|
||||||
height = *(rawMatrix + 1);
|
height = *(rawMatrix + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3.Transform( ref worldPos, ref viewProjectionMatrix, out Vector3 pCoords);
|
Vector3.Transform(ref worldPos, ref viewProjectionMatrix, out Vector3 pCoords);
|
||||||
|
|
||||||
screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z);
|
screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z);
|
||||||
|
|
||||||
screenPos.X = 0.5f * width * (screenPos.X + 1f) + windowPos.X;
|
screenPos.X = (0.5f * width * (screenPos.X + 1f)) + windowPos.X;
|
||||||
screenPos.Y = 0.5f * height * (1f - screenPos.Y) + windowPos.Y;
|
screenPos.Y = (0.5f * height * (1f - screenPos.Y)) + windowPos.Y;
|
||||||
|
|
||||||
return pCoords.Z > 0 &&
|
return pCoords.Z > 0 &&
|
||||||
screenPos.X > windowPos.X && screenPos.X < windowPos.X + width &&
|
screenPos.X > windowPos.X && screenPos.X < windowPos.X + width &&
|
||||||
|
|
@ -336,10 +263,10 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts screen coordinates to in-world coordinates via raycasting.
|
/// Converts screen coordinates to in-world coordinates via raycasting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="screenPos">Screen coordinates</param>
|
/// <param name="screenPos">Screen coordinates.</param>
|
||||||
/// <param name="worldPos">Converted coordinates</param>
|
/// <param name="worldPos">Converted coordinates.</param>
|
||||||
/// <param name="rayDistance">How far to search for a collision</param>
|
/// <param name="rayDistance">How far to search for a collision.</param>
|
||||||
/// <returns>True if successful. On false, worldPos's contents are undefined</returns>
|
/// <returns>True if successful. On false, worldPos's contents are undefined.</returns>
|
||||||
public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f)
|
public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f)
|
||||||
{
|
{
|
||||||
// The game is only visible in the main viewport, so if the cursor is outside
|
// The game is only visible in the main viewport, so if the cursor is outside
|
||||||
|
|
@ -350,7 +277,7 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X ||
|
if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X ||
|
||||||
screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y)
|
screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y)
|
||||||
{
|
{
|
||||||
worldPos = new Vector3();
|
worldPos = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,7 +285,7 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
var matrixSingleton = this.getMatrixSingleton();
|
var matrixSingleton = this.getMatrixSingleton();
|
||||||
|
|
||||||
// Read current ViewProjectionMatrix plus game window size
|
// Read current ViewProjectionMatrix plus game window size
|
||||||
var viewProjectionMatrix = new Matrix();
|
var viewProjectionMatrix = default(Matrix);
|
||||||
float width, height;
|
float width, height;
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|
@ -374,14 +301,15 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
viewProjectionMatrix.Invert();
|
viewProjectionMatrix.Invert();
|
||||||
|
|
||||||
var localScreenPos = new Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y);
|
var localScreenPos = new Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y);
|
||||||
var screenPos3D = new Vector3 {
|
var screenPos3D = new Vector3
|
||||||
X = localScreenPos.X / width * 2.0f - 1.0f,
|
{
|
||||||
Y = -(localScreenPos.Y / height * 2.0f - 1.0f),
|
X = (localScreenPos.X / width * 2.0f) - 1.0f,
|
||||||
Z = 0
|
Y = -((localScreenPos.Y / height * 2.0f) - 1.0f),
|
||||||
|
Z = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos);
|
Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos);
|
||||||
|
|
||||||
screenPos3D.Z = 1;
|
screenPos3D.Z = 1;
|
||||||
Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPosOne);
|
Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPosOne);
|
||||||
|
|
||||||
|
|
@ -389,7 +317,8 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
clipPos.Normalize();
|
clipPos.Normalize();
|
||||||
|
|
||||||
bool isSuccess;
|
bool isSuccess;
|
||||||
unsafe {
|
unsafe
|
||||||
|
{
|
||||||
var camPosArray = camPos.ToArray();
|
var camPosArray = camPos.ToArray();
|
||||||
var clipPosArray = clipPos.ToArray();
|
var clipPosArray = clipPos.ToArray();
|
||||||
|
|
||||||
|
|
@ -397,54 +326,46 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
var worldPosArray = stackalloc float[32];
|
var worldPosArray = stackalloc float[32];
|
||||||
|
|
||||||
// Theory: this is some kind of flag on what type of things the ray collides with
|
// Theory: this is some kind of flag on what type of things the ray collides with
|
||||||
var unknown = stackalloc int[3] { 0x4000, 0x4000, 0x0 };
|
var unknown = stackalloc int[3]
|
||||||
|
{
|
||||||
|
0x4000,
|
||||||
|
0x4000,
|
||||||
|
0x0,
|
||||||
|
};
|
||||||
|
|
||||||
fixed (float* pCamPos = camPosArray) {
|
fixed (float* pCamPos = camPosArray)
|
||||||
fixed (float* pClipPos = clipPosArray) {
|
{
|
||||||
|
fixed (float* pClipPos = clipPosArray)
|
||||||
|
{
|
||||||
isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown);
|
isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
worldPos = new Vector3 {
|
worldPos = new Vector3
|
||||||
|
{
|
||||||
X = worldPosArray[0],
|
X = worldPosArray[0],
|
||||||
Y = worldPosArray[1],
|
Y = worldPosArray[1],
|
||||||
Z = worldPosArray[2]
|
Z = worldPosArray[2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSuccess;
|
return isSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) {
|
|
||||||
GameUiHidden = !GameUiHidden;
|
|
||||||
|
|
||||||
try {
|
|
||||||
OnUiHideToggled?.Invoke(this, GameUiHidden);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Error on OnUiHideToggled event dispatch");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug("UiHide toggled: {0}", GameUiHidden);
|
|
||||||
|
|
||||||
return this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the game's UI module.
|
/// Gets a pointer to the game's UI module.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>IntPtr pointing to UI module</returns>
|
/// <returns>IntPtr pointing to UI module.</returns>
|
||||||
public IntPtr GetUIModule()
|
public IntPtr GetUIModule() => this.getUiModule(this.dalamud.Framework.Address.BaseAddress);
|
||||||
{
|
|
||||||
return this.getUiModule(this.dalamud.Framework.Address.BaseAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the pointer to the UI Object with the given name and index.
|
/// Gets the pointer to the UI Object with the given name and index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">Name of UI to find</param>
|
/// <param name="name">Name of UI to find.</param>
|
||||||
/// <param name="index">Index of UI to find (1-indexed)</param>
|
/// <param name="index">Index of UI to find (1-indexed).</param>
|
||||||
/// <returns>IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the UI Object</returns>
|
/// <returns>IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the UI Object.</returns>
|
||||||
public IntPtr GetUiObjectByName(string name, int index) {
|
public IntPtr GetUiObjectByName(string name, int index)
|
||||||
|
{
|
||||||
var baseUi = this.GetBaseUIObject();
|
var baseUi = this.GetBaseUIObject();
|
||||||
if (baseUi == IntPtr.Zero) return IntPtr.Zero;
|
if (baseUi == IntPtr.Zero) return IntPtr.Zero;
|
||||||
var baseUiProperties = Marshal.ReadIntPtr(baseUi, 0x20);
|
var baseUiProperties = Marshal.ReadIntPtr(baseUi, 0x20);
|
||||||
|
|
@ -452,19 +373,36 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
return this.getUIObjectByName(baseUiProperties, name, index);
|
return this.getUIObjectByName(baseUiProperties, name, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Addon.Addon GetAddonByName(string name, int index) {
|
/// <summary>
|
||||||
var addonMem = GetUiObjectByName(name, index);
|
/// Gets an Addon by it's internal name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The addon name.</param>
|
||||||
|
/// <param name="index">The index of the addon, starting at 1.</param>
|
||||||
|
/// <returns>The native memory representation of the addon, if it exists.</returns>
|
||||||
|
public Addon.Addon GetAddonByName(string name, int index)
|
||||||
|
{
|
||||||
|
var addonMem = this.GetUiObjectByName(name, index);
|
||||||
if (addonMem == IntPtr.Zero) return null;
|
if (addonMem == IntPtr.Zero) return null;
|
||||||
var addonStruct = Marshal.PtrToStructure<Structs.Addon>(addonMem);
|
var addonStruct = Marshal.PtrToStructure<Structs.Addon>(addonMem);
|
||||||
return new Addon.Addon(addonMem, addonStruct);
|
return new Addon.Addon(addonMem, addonStruct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the agent associated with an addon, if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addonName">The addon name.</param>
|
||||||
|
/// <returns>A pointer to the agent interface.</returns>
|
||||||
public IntPtr FindAgentInterface(string addonName)
|
public IntPtr FindAgentInterface(string addonName)
|
||||||
{
|
{
|
||||||
var addon = this.dalamud.Framework.Gui.GetUiObjectByName(addonName, 1);
|
var addon = this.dalamud.Framework.Gui.GetUiObjectByName(addonName, 1);
|
||||||
return this.FindAgentInterface(addon);
|
return this.FindAgentInterface(addon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the agent associated with an addon, if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addon">The addon address.</param>
|
||||||
|
/// <returns>A pointer to the agent interface.</returns>
|
||||||
public IntPtr FindAgentInterface(IntPtr addon)
|
public IntPtr FindAgentInterface(IntPtr addon)
|
||||||
{
|
{
|
||||||
if (addon == IntPtr.Zero)
|
if (addon == IntPtr.Zero)
|
||||||
|
|
@ -501,12 +439,20 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
return IntPtr.Zero;
|
return IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
|
/// <summary>
|
||||||
|
/// Set the current background music.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bgmKey">The background music key.</param>
|
||||||
|
public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
public void Enable() {
|
/// <summary>
|
||||||
Chat.Enable();
|
/// Enables the hooks and submodules of this module.
|
||||||
Toast.Enable();
|
/// </summary>
|
||||||
PartyFinder.Enable();
|
public void Enable()
|
||||||
|
{
|
||||||
|
this.Chat.Enable();
|
||||||
|
this.Toast.Enable();
|
||||||
|
this.PartyFinder.Enable();
|
||||||
this.setGlobalBgmHook.Enable();
|
this.setGlobalBgmHook.Enable();
|
||||||
this.handleItemHoverHook.Enable();
|
this.handleItemHoverHook.Enable();
|
||||||
this.handleItemOutHook.Enable();
|
this.handleItemOutHook.Enable();
|
||||||
|
|
@ -515,10 +461,14 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
this.handleActionOutHook.Enable();
|
this.handleActionOutHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
/// <summary>
|
||||||
Chat.Dispose();
|
/// Disables the hooks and submodules of this module.
|
||||||
Toast.Dispose();
|
/// </summary>
|
||||||
PartyFinder.Dispose();
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Chat.Dispose();
|
||||||
|
this.Toast.Dispose();
|
||||||
|
this.PartyFinder.Dispose();
|
||||||
this.setGlobalBgmHook.Dispose();
|
this.setGlobalBgmHook.Dispose();
|
||||||
this.handleItemHoverHook.Dispose();
|
this.handleItemHoverHook.Dispose();
|
||||||
this.handleItemOutHook.Dispose();
|
this.handleItemOutHook.Dispose();
|
||||||
|
|
@ -526,5 +476,132 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
this.handleActionHoverHook.Dispose();
|
this.handleActionHoverHook.Dispose();
|
||||||
this.handleActionOutHook.Dispose();
|
this.handleActionOutHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6)
|
||||||
|
{
|
||||||
|
var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
|
||||||
|
|
||||||
|
Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||||
|
{
|
||||||
|
var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
|
||||||
|
|
||||||
|
if (retVal.ToInt64() == 22)
|
||||||
|
{
|
||||||
|
var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
|
||||||
|
this.HoveredItem = itemId;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.HoveredItemChanged?.Invoke(this, itemId);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||||
|
{
|
||||||
|
var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
|
||||||
|
|
||||||
|
if (a3 != IntPtr.Zero && a4 == 1)
|
||||||
|
{
|
||||||
|
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||||
|
|
||||||
|
if (a3Val == 255)
|
||||||
|
{
|
||||||
|
this.HoveredItem = 0ul;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.HoveredItemChanged?.Invoke(this, 0ul);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Verbose("HoverItemId: 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
|
||||||
|
{
|
||||||
|
this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
|
||||||
|
this.HoveredAction.ActionKind = actionKind;
|
||||||
|
this.HoveredAction.BaseActionID = actionId;
|
||||||
|
this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
|
||||||
|
{
|
||||||
|
var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4);
|
||||||
|
|
||||||
|
if (a3 != IntPtr.Zero && a4 == 1)
|
||||||
|
{
|
||||||
|
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||||
|
|
||||||
|
if (a3Val == 255)
|
||||||
|
{
|
||||||
|
this.HoveredAction.ActionKind = HoverActionKind.None;
|
||||||
|
this.HoveredAction.BaseActionID = 0;
|
||||||
|
this.HoveredAction.ActionID = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not dispatch HoveredActionChanged event.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Verbose("HoverActionId: 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte)
|
||||||
|
{
|
||||||
|
this.GameUiHidden = !this.GameUiHidden;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OnUiHideToggled?.Invoke(this, this.GameUiHidden);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error on OnUiHideToggled event dispatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("UiHide toggled: {0}", this.GameUiHidden);
|
||||||
|
|
||||||
|
return this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,123 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="GameGui"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameGuiAddressResolver"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseAddress">The base address of the native GuiManager class.</param>
|
||||||
|
public GameGuiAddressResolver(IntPtr baseAddress)
|
||||||
|
{
|
||||||
|
this.BaseAddress = baseAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the base address of the native GuiManager class.
|
||||||
|
/// </summary>
|
||||||
public IntPtr BaseAddress { get; private set; }
|
public IntPtr BaseAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ChatManager class.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ChatManager { get; private set; }
|
public IntPtr ChatManager { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native SetGlobalBgm method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr SetGlobalBgm { get; private set; }
|
public IntPtr SetGlobalBgm { get; private set; }
|
||||||
public IntPtr HandleItemHover { get; set; }
|
|
||||||
public IntPtr HandleItemOut { get; set; }
|
/// <summary>
|
||||||
public IntPtr HandleActionHover { get; set; }
|
/// Gets the address of the native HandleItemHover method.
|
||||||
public IntPtr HandleActionOut { get; set; }
|
/// </summary>
|
||||||
|
public IntPtr HandleItemHover { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleItemOut method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleItemOut { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleActionHover method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleActionHover { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleActionOut method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleActionOut { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native GetUIObject method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetUIObject { get; private set; }
|
public IntPtr GetUIObject { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native GetMatrixSingleton method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetMatrixSingleton { get; private set; }
|
public IntPtr GetMatrixSingleton { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ScreenToWorld method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ScreenToWorld { get; private set; }
|
public IntPtr ScreenToWorld { get; private set; }
|
||||||
public IntPtr ToggleUiHide { get; set; }
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ToggleUiHide method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr ToggleUiHide { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native Client::UI::UIModule getter method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetBaseUIObject { get; private set; }
|
public IntPtr GetBaseUIObject { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native GetUIObjectByName method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetUIObjectByName { get; private set; }
|
public IntPtr GetUIObjectByName { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native GetUIModule method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetUIModule { get; private set; }
|
public IntPtr GetUIModule { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native GetAgentModule method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetAgentModule { get; private set; }
|
public IntPtr GetAgentModule { get; private set; }
|
||||||
|
|
||||||
public GameGuiAddressResolver(IntPtr baseAddress) {
|
/// <inheritdoc/>
|
||||||
BaseAddress = baseAddress;
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
}
|
{
|
||||||
|
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||||
private delegate IntPtr GetChatManagerDelegate(IntPtr guiManager);
|
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");
|
||||||
protected override void SetupInternal(SigScanner scanner) {
|
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
this.GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
|
||||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||||
ChatManager = BaseAddress + 0x13E0;
|
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||||
}
|
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||||
|
this.GetBaseUIObject = sig.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
this.GetUIObjectByName = sig.ScanText("E8 ?? ?? ?? ?? 48 8B CF 48 89 87 ?? ?? 00 00 E8 ?? ?? ?? ?? 41 B8 01 00 00 00");
|
||||||
SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
this.GetUIModule = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 85 C0 75 2D");
|
||||||
HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
|
||||||
HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
|
||||||
HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
|
||||||
HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
|
||||||
GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
|
|
||||||
GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
|
||||||
ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
|
||||||
ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
|
||||||
GetBaseUIObject = sig.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
|
|
||||||
GetUIObjectByName = sig.ScanText("E8 ?? ?? ?? ?? 48 8B CF 48 89 87 ?? ?? 00 00 E8 ?? ?? ?? ?? 41 B8 01 00 00 00");
|
|
||||||
GetUIModule = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 85 C0 75 2D");
|
|
||||||
|
|
||||||
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/>
|
||||||
|
protected override void SetupInternal(SigScanner scanner)
|
||||||
|
{
|
||||||
|
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
||||||
|
// Xiv__UiManager__GetChatManager+7 000 retn
|
||||||
|
this.ChatManager = this.BaseAddress + 0x13E0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,49 @@
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ActionKinds used in AgentActionDetail.
|
/// ActionKinds used in AgentActionDetail.
|
||||||
|
/// These describe the possible kinds of actions being hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum HoverActionKind {
|
public enum HoverActionKind
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No action is hovered.
|
||||||
|
/// </summary>
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A regular action is hovered.
|
||||||
|
/// </summary>
|
||||||
Action = 21,
|
Action = 21,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A general action is hovered.
|
||||||
|
/// </summary>
|
||||||
GeneralAction = 23,
|
GeneralAction = 23,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A companion order type of action is hovered.
|
||||||
|
/// </summary>
|
||||||
CompanionOrder = 24,
|
CompanionOrder = 24,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A main command type of action is hovered.
|
||||||
|
/// </summary>
|
||||||
MainCommand = 25,
|
MainCommand = 25,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An extras command type of action is hovered.
|
||||||
|
/// </summary>
|
||||||
ExtraCommand = 26,
|
ExtraCommand = 26,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A pet order type of action is hovered.
|
||||||
|
/// </summary>
|
||||||
PetOrder = 28,
|
PetOrder = 28,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A trait is hovered.
|
||||||
|
/// </summary>
|
||||||
Trait = 29,
|
Trait = 29,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
public class HoveredAction {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents the hotbar action currently hovered over by the cursor.
|
||||||
|
/// </summary>
|
||||||
|
public class HoveredAction
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base action ID
|
/// Gets or sets the base action ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint BaseActionID { get; set; } = 0;
|
public uint BaseActionID { get; set; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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>
|
||||||
/// 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
class PartyFinderAddressResolver : BaseAddressResolver {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal class PartyFinderAddressResolver : BaseAddressResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ReceiveListing method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ReceiveListing { get; private set; }
|
public IntPtr ReceiveListing { get; private set; }
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
/// <inheritdoc/>
|
||||||
ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
|
{
|
||||||
|
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,90 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.Internal.Gui.Structs;
|
using Dalamud.Game.Internal.Gui.Structs;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
namespace Dalamud.Game.Internal.Gui
|
||||||
public sealed class PartyFinderGui : IDisposable {
|
{
|
||||||
#region Events
|
/// <summary>
|
||||||
|
/// This class handles interacting with the native PartyFinder window.
|
||||||
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
/// </summary>
|
||||||
|
public sealed class PartyFinderGui : IDisposable
|
||||||
/// <summary>
|
{
|
||||||
/// Event fired each time the game receives an individual Party Finder listing. Cannot modify listings but can
|
private readonly Dalamud dalamud;
|
||||||
/// hide them.
|
private readonly PartyFinderAddressResolver address;
|
||||||
/// </summary>
|
private readonly IntPtr memory;
|
||||||
public event PartyFinderListingEventDelegate ReceiveListing;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Hooks
|
|
||||||
|
|
||||||
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
||||||
|
|
||||||
#endregion
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public PartyFinderGui(SigScanner scanner, Dalamud dalamud)
|
||||||
|
{
|
||||||
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
#region Delegates
|
this.address = new PartyFinderAddressResolver();
|
||||||
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
|
this.memory = Marshal.AllocHGlobal(PartyFinder.PacketInfo.PacketSize);
|
||||||
|
|
||||||
|
this.receiveListingHook = new Hook<ReceiveListingDelegate>(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event type fired each time the game receives an individual Party Finder listing.
|
||||||
|
/// Cannot modify listings but can hide them.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listing">The listings received.</param>
|
||||||
|
/// <param name="args">Additional arguments passed by the game.</param>
|
||||||
|
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
||||||
|
|
||||||
#endregion
|
/// <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;
|
||||||
|
|
||||||
private Dalamud Dalamud { get; }
|
/// <summary>
|
||||||
private PartyFinderAddressResolver Address { get; }
|
/// Enables this module.
|
||||||
private IntPtr Memory { get; }
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
public PartyFinderGui(SigScanner scanner, Dalamud dalamud) {
|
{
|
||||||
Dalamud = dalamud;
|
|
||||||
|
|
||||||
Address = new PartyFinderAddressResolver();
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Memory = Marshal.AllocHGlobal(PartyFinder.PacketInfo.PacketSize);
|
|
||||||
|
|
||||||
this.receiveListingHook = new Hook<ReceiveListingDelegate>(Address.ReceiveListing, new ReceiveListingDelegate(HandleReceiveListingDetour));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.receiveListingHook.Enable();
|
this.receiveListingHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
/// <summary>
|
||||||
|
/// Dispose of m anaged and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
this.receiveListingHook.Dispose();
|
this.receiveListingHook.Dispose();
|
||||||
Marshal.FreeHGlobal(Memory);
|
Marshal.FreeHGlobal(this.memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) {
|
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
||||||
try {
|
{
|
||||||
HandleListingEvents(data);
|
try
|
||||||
} catch (Exception ex) {
|
{
|
||||||
|
this.HandleListingEvents(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.receiveListingHook.Original(managerPtr, data);
|
this.receiveListingHook.Original(managerPtr, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleListingEvents(IntPtr data) {
|
private void HandleListingEvents(IntPtr data)
|
||||||
|
{
|
||||||
var dataPtr = data + 0x10;
|
var dataPtr = data + 0x10;
|
||||||
|
|
||||||
var packet = Marshal.PtrToStructure<PartyFinder.Packet>(dataPtr);
|
var packet = Marshal.PtrToStructure<PartyFinder.Packet>(dataPtr);
|
||||||
|
|
@ -73,51 +92,66 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
// rewriting is an expensive operation, so only do it if necessary
|
// rewriting is an expensive operation, so only do it if necessary
|
||||||
var needToRewrite = false;
|
var needToRewrite = false;
|
||||||
|
|
||||||
for (var i = 0; i < packet.listings.Length; i++) {
|
for (var i = 0; i < packet.Listings.Length; i++)
|
||||||
|
{
|
||||||
// these are empty slots that are not shown to the player
|
// these are empty slots that are not shown to the player
|
||||||
if (packet.listings[i].IsNull()) {
|
if (packet.Listings[i].IsNull())
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var listing = new PartyFinderListing(packet.listings[i], Dalamud.Data, Dalamud.SeStringManager);
|
var listing = new PartyFinderListing(packet.Listings[i], this.dalamud.Data, this.dalamud.SeStringManager);
|
||||||
var args = new PartyFinderListingEventArgs(packet.batchNumber);
|
var args = new PartyFinderListingEventArgs(packet.BatchNumber);
|
||||||
ReceiveListing?.Invoke(listing, args);
|
this.ReceiveListing?.Invoke(listing, args);
|
||||||
|
|
||||||
if (args.Visible) {
|
if (args.Visible)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide the listing from the player by setting it to a null listing
|
// hide the listing from the player by setting it to a null listing
|
||||||
packet.listings[i] = new PartyFinder.Listing();
|
packet.Listings[i] = default;
|
||||||
needToRewrite = true;
|
needToRewrite = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needToRewrite) {
|
if (!needToRewrite)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write our struct into the memory (doing this directly crashes the game)
|
// write our struct into the memory (doing this directly crashes the game)
|
||||||
Marshal.StructureToPtr(packet, Memory, false);
|
Marshal.StructureToPtr(packet, this.memory, false);
|
||||||
|
|
||||||
// copy our new memory over the game's
|
// copy our new memory over the game's
|
||||||
unsafe {
|
unsafe
|
||||||
Buffer.MemoryCopy(
|
{
|
||||||
(void*) Memory,
|
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinder.PacketInfo.PacketSize, PartyFinder.PacketInfo.PacketSize);
|
||||||
(void*) dataPtr,
|
|
||||||
PartyFinder.PacketInfo.PacketSize,
|
|
||||||
PartyFinder.PacketInfo.PacketSize
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PartyFinderListingEventArgs {
|
/// <summary>
|
||||||
|
/// This class represents additional arguments passed by the game.
|
||||||
|
/// </summary>
|
||||||
|
public class PartyFinderListingEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PartyFinderListingEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="batchNumber">The batch number.</param>
|
||||||
|
internal PartyFinderListingEventArgs(int batchNumber)
|
||||||
|
{
|
||||||
|
this.BatchNumber = batchNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the batch number.
|
||||||
|
/// </summary>
|
||||||
public int BatchNumber { get; }
|
public int BatchNumber { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the listing is visible.
|
||||||
|
/// </summary>
|
||||||
public bool Visible { get; set; } = true;
|
public bool Visible { get; set; } = true;
|
||||||
|
|
||||||
internal PartyFinderListingEventArgs(int batchNumber) {
|
|
||||||
BatchNumber = batchNumber;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,59 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui.Structs {
|
namespace Dalamud.Game.Internal.Gui.Structs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Native memory representation of an FFXIV UI addon.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public struct Addon
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the addon.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(AddonOffsets.Name)]
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
|
||||||
|
public string Name;
|
||||||
|
|
||||||
public class AddonOffsets {
|
/// <summary>
|
||||||
|
/// Various flags that can be set on the addon.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is a bitfield.
|
||||||
|
/// </remarks>
|
||||||
|
[FieldOffset(AddonOffsets.Flags)]
|
||||||
|
public byte Flags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The X position of the addon on screen.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(AddonOffsets.X)]
|
||||||
|
public short X;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Y position of the addon on screen.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(AddonOffsets.Y)]
|
||||||
|
public short Y;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The scale of the addon.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(AddonOffsets.Scale)]
|
||||||
|
public float Scale;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The root node of the addon's node tree.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(AddonOffsets.RootNode)]
|
||||||
|
public unsafe AtkResNode* RootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Memory offsets for the <see cref="Addon"/> type.
|
||||||
|
/// </summary>
|
||||||
|
public static class AddonOffsets
|
||||||
|
{
|
||||||
public const int Name = 0x8;
|
public const int Name = 0x8;
|
||||||
public const int RootNode = 0xC8;
|
public const int RootNode = 0xC8;
|
||||||
public const int Flags = 0x182;
|
public const int Flags = 0x182;
|
||||||
|
|
@ -10,17 +61,4 @@ namespace Dalamud.Game.Internal.Gui.Structs {
|
||||||
public const int Y = 0x1BE;
|
public const int Y = 0x1BE;
|
||||||
public const int Scale = 0x1AC;
|
public const int Scale = 0x1AC;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct Addon {
|
|
||||||
[FieldOffset(AddonOffsets.Name), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
|
|
||||||
public string Name;
|
|
||||||
|
|
||||||
[FieldOffset(AddonOffsets.Flags)] public byte Flags;
|
|
||||||
[FieldOffset(AddonOffsets.X)] public short X;
|
|
||||||
[FieldOffset(AddonOffsets.Y)] public short Y;
|
|
||||||
[FieldOffset(AddonOffsets.Scale)] public float Scale;
|
|
||||||
[FieldOffset(AddonOffsets.RootNode)] public unsafe AtkResNode* RootNode;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,130 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui.Structs {
|
namespace Dalamud.Game.Internal.Gui.Structs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Native memory representation of a UI resource node.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is copied from https://github.com/aers/FFXIVClientStructs/blob/main/Component/GUI/AtkResNode.cs.
|
||||||
|
/// If you need newer features, include FFXIVClientStructs and ILMerge the assembly.
|
||||||
|
/// </remarks>
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 0xA8)]
|
[StructLayout(LayoutKind.Explicit, Size = 0xA8)]
|
||||||
|
public unsafe struct AtkResNode
|
||||||
|
{
|
||||||
|
[FieldOffset(0x0)]
|
||||||
|
public IntPtr AtkEventTarget;
|
||||||
|
|
||||||
// https://github.com/aers/FFXIVClientStructs/blob/main/Component/GUI/AtkResNode.cs
|
[FieldOffset(0x8)]
|
||||||
public unsafe struct AtkResNode {
|
public uint NodeID;
|
||||||
[FieldOffset(0x0)] public IntPtr AtkEventTarget;
|
|
||||||
[FieldOffset(0x8)] public uint NodeID;
|
[FieldOffset(0x20)]
|
||||||
[FieldOffset(0x20)] public AtkResNode* ParentNode;
|
public AtkResNode* ParentNode;
|
||||||
[FieldOffset(0x28)] public AtkResNode* PrevSiblingNode;
|
|
||||||
[FieldOffset(0x30)] public AtkResNode* NextSiblingNode;
|
[FieldOffset(0x28)]
|
||||||
[FieldOffset(0x38)] public AtkResNode* ChildNode;
|
public AtkResNode* PrevSiblingNode;
|
||||||
[FieldOffset(0x40)] public ushort Type;
|
|
||||||
[FieldOffset(0x42)] public ushort ChildCount;
|
[FieldOffset(0x30)]
|
||||||
[FieldOffset(0x44)] public float X;
|
public AtkResNode* NextSiblingNode;
|
||||||
[FieldOffset(0x48)] public float Y;
|
|
||||||
[FieldOffset(0x4C)] public float ScaleX;
|
[FieldOffset(0x38)]
|
||||||
[FieldOffset(0x50)] public float ScaleY;
|
public AtkResNode* ChildNode;
|
||||||
[FieldOffset(0x54)] public float Rotation;
|
|
||||||
[FieldOffset(0x58)] public fixed float UnkMatrix[3 * 2];
|
[FieldOffset(0x40)]
|
||||||
[FieldOffset(0x70)] public uint Color;
|
public ushort Type;
|
||||||
[FieldOffset(0x74)] public float Depth;
|
|
||||||
[FieldOffset(0x78)] public float Depth_2;
|
[FieldOffset(0x42)]
|
||||||
[FieldOffset(0x7C)] public ushort AddRed;
|
public ushort ChildCount;
|
||||||
[FieldOffset(0x7E)] public ushort AddGreen;
|
|
||||||
[FieldOffset(0x80)] public ushort AddBlue;
|
[FieldOffset(0x44)]
|
||||||
[FieldOffset(0x82)] public ushort AddRed_2;
|
public float X;
|
||||||
[FieldOffset(0x84)] public ushort AddGreen_2;
|
|
||||||
[FieldOffset(0x86)] public ushort AddBlue_2;
|
[FieldOffset(0x48)]
|
||||||
[FieldOffset(0x88)] public byte MultiplyRed;
|
public float Y;
|
||||||
[FieldOffset(0x89)] public byte MultiplyGreen;
|
|
||||||
[FieldOffset(0x8A)] public byte MultiplyBlue;
|
[FieldOffset(0x4C)]
|
||||||
[FieldOffset(0x8B)] public byte MultiplyRed_2;
|
public float ScaleX;
|
||||||
[FieldOffset(0x8C)] public byte MultiplyGreen_2;
|
|
||||||
[FieldOffset(0x8D)] public byte MultiplyBlue_2;
|
[FieldOffset(0x50)]
|
||||||
[FieldOffset(0x8E)] public byte Alpha_2;
|
public float ScaleY;
|
||||||
[FieldOffset(0x8F)] public byte UnkByte_1;
|
|
||||||
[FieldOffset(0x90)] public ushort Width;
|
[FieldOffset(0x54)]
|
||||||
[FieldOffset(0x92)] public ushort Height;
|
public float Rotation;
|
||||||
[FieldOffset(0x94)] public float OriginX;
|
|
||||||
[FieldOffset(0x98)] public float OriginY;
|
[FieldOffset(0x58)]
|
||||||
[FieldOffset(0x9C)] public ushort Priority;
|
public fixed float UnkMatrix[3 * 2];
|
||||||
[FieldOffset(0x9E)] public short Flags;
|
|
||||||
[FieldOffset(0xA0)] public uint Flags_2;
|
[FieldOffset(0x70)]
|
||||||
|
public uint Color;
|
||||||
|
|
||||||
|
[FieldOffset(0x74)]
|
||||||
|
public float Depth;
|
||||||
|
|
||||||
|
[FieldOffset(0x78)]
|
||||||
|
public float Depth_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x7C)]
|
||||||
|
public ushort AddRed;
|
||||||
|
|
||||||
|
[FieldOffset(0x7E)]
|
||||||
|
public ushort AddGreen;
|
||||||
|
|
||||||
|
[FieldOffset(0x80)]
|
||||||
|
public ushort AddBlue;
|
||||||
|
|
||||||
|
[FieldOffset(0x82)]
|
||||||
|
public ushort AddRed_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x84)]
|
||||||
|
public ushort AddGreen_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x86)]
|
||||||
|
public ushort AddBlue_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x88)]
|
||||||
|
public byte MultiplyRed;
|
||||||
|
|
||||||
|
[FieldOffset(0x89)]
|
||||||
|
public byte MultiplyGreen;
|
||||||
|
|
||||||
|
[FieldOffset(0x8A)]
|
||||||
|
public byte MultiplyBlue;
|
||||||
|
|
||||||
|
[FieldOffset(0x8B)]
|
||||||
|
public byte MultiplyRed_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x8C)]
|
||||||
|
public byte MultiplyGreen_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x8D)]
|
||||||
|
public byte MultiplyBlue_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x8E)]
|
||||||
|
public byte Alpha_2;
|
||||||
|
|
||||||
|
[FieldOffset(0x8F)]
|
||||||
|
public byte UnkByte_1;
|
||||||
|
|
||||||
|
[FieldOffset(0x90)]
|
||||||
|
public ushort Width;
|
||||||
|
|
||||||
|
[FieldOffset(0x92)]
|
||||||
|
public ushort Height;
|
||||||
|
|
||||||
|
[FieldOffset(0x94)]
|
||||||
|
public float OriginX;
|
||||||
|
|
||||||
|
[FieldOffset(0x98)]
|
||||||
|
public float OriginY;
|
||||||
|
|
||||||
|
[FieldOffset(0x9C)]
|
||||||
|
public ushort Priority;
|
||||||
|
|
||||||
|
[FieldOffset(0x9E)]
|
||||||
|
public short Flags;
|
||||||
|
|
||||||
|
[FieldOffset(0xA0)]
|
||||||
|
public uint Flags_2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,455 +1,129 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui.Structs {
|
|
||||||
#region Raw structs
|
|
||||||
|
|
||||||
internal static class PartyFinder {
|
|
||||||
public static class PacketInfo {
|
|
||||||
public static readonly int PacketSize = Marshal.SizeOf<Packet>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Internal.Gui.Structs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// PartyFinder related network structs and static constants.
|
||||||
|
/// </summary>
|
||||||
|
internal static class PartyFinder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The structure of the PartyFinder packet.
|
||||||
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public readonly struct Packet {
|
internal readonly struct Packet
|
||||||
public 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)]
|
||||||
public readonly Listing[] listings;
|
internal readonly Listing[] Listings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The structure of an individual listing within a packet.
|
||||||
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public readonly struct Listing {
|
internal readonly struct Listing
|
||||||
|
{
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||||
private readonly byte[] header1;
|
private readonly byte[] header1;
|
||||||
|
|
||||||
internal readonly uint id;
|
internal readonly uint Id;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||||
private readonly byte[] header2;
|
private readonly byte[] header2;
|
||||||
|
|
||||||
internal readonly uint contentIdLower;
|
internal readonly uint ContentIdLower;
|
||||||
private readonly ushort unknownShort1;
|
private readonly ushort unknownShort1;
|
||||||
private readonly ushort unknownShort2;
|
private readonly ushort unknownShort2;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||||
private readonly byte[] header3;
|
private readonly byte[] header3;
|
||||||
|
|
||||||
internal readonly byte category;
|
internal readonly byte Category;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||||
private readonly byte[] header4;
|
private readonly byte[] header4;
|
||||||
|
|
||||||
internal readonly ushort duty;
|
internal readonly ushort Duty;
|
||||||
internal readonly byte dutyType;
|
internal readonly byte DutyType;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
||||||
private readonly byte[] header5;
|
private readonly byte[] header5;
|
||||||
|
|
||||||
internal readonly ushort world;
|
internal readonly ushort World;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
private readonly byte[] header6;
|
private readonly byte[] header6;
|
||||||
|
|
||||||
internal readonly byte objective;
|
internal readonly byte Objective;
|
||||||
internal readonly byte beginnersWelcome;
|
internal readonly byte BeginnersWelcome;
|
||||||
internal readonly byte conditions;
|
internal readonly byte Conditions;
|
||||||
internal readonly byte dutyFinderSettings;
|
internal readonly byte DutyFinderSettings;
|
||||||
internal readonly byte lootRules;
|
internal readonly byte LootRules;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||||
private readonly byte[] header7; // all zero in every pf I've examined
|
private readonly byte[] header7; // all zero in every pf I've examined
|
||||||
|
|
||||||
private readonly uint lastPatchHotfixTimestamp; // last time the servers were restarted?
|
private readonly uint lastPatchHotfixTimestamp; // last time the servers were restarted?
|
||||||
internal readonly ushort secondsRemaining;
|
internal readonly ushort SecondsRemaining;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
|
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
|
||||||
|
|
||||||
internal readonly ushort minimumItemLevel;
|
internal readonly ushort MinimumItemLevel;
|
||||||
internal readonly ushort homeWorld;
|
internal readonly ushort HomeWorld;
|
||||||
internal readonly ushort currentWorld;
|
internal readonly ushort CurrentWorld;
|
||||||
|
|
||||||
private readonly byte header9;
|
private readonly byte header9;
|
||||||
|
|
||||||
internal readonly byte numSlots;
|
internal readonly byte NumSlots;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||||
private readonly byte[] header10;
|
private readonly byte[] header10;
|
||||||
|
|
||||||
internal readonly byte searchArea;
|
internal readonly byte SearchArea;
|
||||||
|
|
||||||
private readonly byte header11;
|
private readonly byte header11;
|
||||||
|
|
||||||
internal readonly byte numParties;
|
internal readonly byte NumParties;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||||
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
|
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
internal readonly uint[] slots;
|
internal readonly uint[] Slots;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
internal readonly byte[] jobsPresent;
|
internal readonly byte[] JobsPresent;
|
||||||
|
|
||||||
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
|
// 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)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||||
internal readonly byte[] name;
|
internal readonly byte[] Name;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
|
||||||
internal readonly byte[] description;
|
internal readonly byte[] Description;
|
||||||
|
|
||||||
internal bool IsNull() {
|
internal bool IsNull()
|
||||||
|
{
|
||||||
// a valid party finder must have at least one slot set
|
// a valid party finder must have at least one slot set
|
||||||
return this.slots.All(slot => slot == 0);
|
return this.Slots.All(slot => slot == 0);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Read-only classes
|
|
||||||
|
|
||||||
public class PartyFinderListing {
|
|
||||||
/// <summary>
|
|
||||||
/// The ID assigned to this listing by the game's server.
|
|
||||||
/// </summary>
|
|
||||||
public uint Id { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The lower bits of the player's content ID.
|
|
||||||
/// </summary>
|
|
||||||
public uint ContentIdLower { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the player hosting this listing.
|
|
||||||
/// </summary>
|
|
||||||
public SeString Name { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The description of this listing as set by the host. May be multiple lines.
|
|
||||||
/// </summary>
|
|
||||||
public SeString Description { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The world that this listing was created on.
|
|
||||||
/// </summary>
|
|
||||||
public Lazy<World> World { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The home world of the listing's host.
|
|
||||||
/// </summary>
|
|
||||||
public Lazy<World> HomeWorld { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The current world of the listing's host.
|
|
||||||
/// </summary>
|
|
||||||
public Lazy<World> CurrentWorld { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The Party Finder category this listing is listed under.
|
|
||||||
/// </summary>
|
|
||||||
public Category Category { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The row ID of the duty this listing is for. May be 0 for non-duty listings.
|
|
||||||
/// </summary>
|
|
||||||
public ushort RawDuty { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The duty this listing is for. May be null for non-duty listings.
|
|
||||||
/// </summary>
|
|
||||||
public Lazy<ContentFinderCondition> Duty { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The type of duty this listing is for.
|
|
||||||
/// </summary>
|
|
||||||
public DutyType DutyType { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// If this listing is beginner-friendly. Shown with a sprout icon in-game.
|
|
||||||
/// </summary>
|
|
||||||
public bool BeginnersWelcome { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// How many seconds this listing will continue to be available for. It may end before this time if the party
|
|
||||||
/// fills or the host ends it early.
|
|
||||||
/// </summary>
|
|
||||||
public ushort SecondsRemaining { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The minimum item level required to join this listing.
|
|
||||||
/// </summary>
|
|
||||||
public ushort MinimumItemLevel { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The number of parties this listing is recruiting for.
|
|
||||||
/// </summary>
|
|
||||||
public byte Parties { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The number of player slots this listing is recruiting for.
|
|
||||||
/// </summary>
|
|
||||||
public byte SlotsAvailable { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of player slots that the Party Finder is accepting.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyCollection<PartyFinderSlot> Slots => this.slots;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The objective of this listing.
|
|
||||||
/// </summary>
|
|
||||||
public ObjectiveFlags Objective => (ObjectiveFlags) this.objective;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The conditions of this listing.
|
|
||||||
/// </summary>
|
|
||||||
public ConditionFlags Conditions => (ConditionFlags) this.conditions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Duty Finder settings that will be used for this listing.
|
|
||||||
/// </summary>
|
|
||||||
public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags) this.dutyFinderSettings;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The loot rules that will be used for this listing.
|
|
||||||
/// </summary>
|
|
||||||
public LootRuleFlags LootRules => (LootRuleFlags) this.lootRules;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Where this listing is searching. Note that this is also used for denoting alliance raid listings and one
|
|
||||||
/// player per job.
|
|
||||||
/// </summary>
|
|
||||||
public SearchAreaFlags SearchArea => (SearchAreaFlags) this.searchArea;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of the class/job IDs that are currently present in the party.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyCollection<byte> RawJobsPresent => this.jobsPresent;
|
|
||||||
/// <summary>
|
|
||||||
/// A list of the classes/jobs that are currently present in the party.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
|
|
||||||
|
|
||||||
#region Backing fields
|
|
||||||
|
|
||||||
private readonly byte objective;
|
|
||||||
private readonly byte conditions;
|
|
||||||
private readonly byte dutyFinderSettings;
|
|
||||||
private readonly byte lootRules;
|
|
||||||
private readonly byte searchArea;
|
|
||||||
private readonly PartyFinderSlot[] slots;
|
|
||||||
private readonly byte[] jobsPresent;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Indexers
|
|
||||||
|
|
||||||
public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint) flag) > 0;
|
|
||||||
|
|
||||||
public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint) flag) > 0;
|
|
||||||
|
|
||||||
public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint) flag) > 0;
|
|
||||||
|
|
||||||
public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint) flag) > 0;
|
|
||||||
|
|
||||||
public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint) flag) > 0;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
internal PartyFinderListing(PartyFinder.Listing listing, DataManager dataManager, SeStringManager seStringManager) {
|
|
||||||
this.objective = listing.objective;
|
|
||||||
this.conditions = listing.conditions;
|
|
||||||
this.dutyFinderSettings = listing.dutyFinderSettings;
|
|
||||||
this.lootRules = listing.lootRules;
|
|
||||||
this.searchArea = listing.searchArea;
|
|
||||||
this.slots = listing.slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray();
|
|
||||||
this.jobsPresent = listing.jobsPresent;
|
|
||||||
|
|
||||||
Id = listing.id;
|
|
||||||
ContentIdLower = listing.contentIdLower;
|
|
||||||
Name = seStringManager.Parse(listing.name.TakeWhile(b => b != 0).ToArray());
|
|
||||||
Description = seStringManager.Parse(listing.description.TakeWhile(b => b != 0).ToArray());
|
|
||||||
World = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.world));
|
|
||||||
HomeWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.homeWorld));
|
|
||||||
CurrentWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.currentWorld));
|
|
||||||
Category = (Category) listing.category;
|
|
||||||
RawDuty = listing.duty;
|
|
||||||
Duty = new Lazy<ContentFinderCondition>(() => dataManager.GetExcelSheet<ContentFinderCondition>().GetRow(listing.duty));
|
|
||||||
DutyType = (DutyType) listing.dutyType;
|
|
||||||
BeginnersWelcome = listing.beginnersWelcome == 1;
|
|
||||||
SecondsRemaining = listing.secondsRemaining;
|
|
||||||
MinimumItemLevel = listing.minimumItemLevel;
|
|
||||||
Parties = listing.numParties;
|
|
||||||
SlotsAvailable = listing.numSlots;
|
|
||||||
JobsPresent = listing.jobsPresent
|
|
||||||
.Select(id => new Lazy<ClassJob>(() => id == 0
|
|
||||||
? null
|
|
||||||
: dataManager.GetExcelSheet<ClassJob>().GetRow(id)))
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A player slot in a Party Finder listing.
|
|
||||||
/// </summary>
|
|
||||||
public class PartyFinderSlot {
|
|
||||||
private readonly uint accepting;
|
|
||||||
private JobFlags[] listAccepting;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of jobs that this slot is accepting.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyCollection<JobFlags> Accepting {
|
|
||||||
get {
|
|
||||||
if (this.listAccepting != null) {
|
|
||||||
return this.listAccepting;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listAccepting = Enum.GetValues(typeof(JobFlags))
|
|
||||||
.Cast<JobFlags>()
|
|
||||||
.Where(flag => this[flag])
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return this.listAccepting;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests if this slot is accepting a job.
|
/// PartyFinder packet constants.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="flag">Job to test</param>
|
public static class PacketInfo
|
||||||
public bool this[JobFlags flag] => (this.accepting & (uint) flag) > 0;
|
{
|
||||||
|
/// <summary>
|
||||||
internal PartyFinderSlot(uint accepting) {
|
/// The size of the PartyFinder packet.
|
||||||
this.accepting = accepting;
|
/// </summary>
|
||||||
|
public static readonly int PacketSize = Marshal.SizeOf<Packet>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum SearchAreaFlags : uint {
|
|
||||||
DataCentre = 1 << 0,
|
|
||||||
Private = 1 << 1,
|
|
||||||
AllianceRaid = 1 << 2,
|
|
||||||
World = 1 << 3,
|
|
||||||
OnePlayerPerJob = 1 << 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum JobFlags {
|
|
||||||
Gladiator = 1 << 1,
|
|
||||||
Pugilist = 1 << 2,
|
|
||||||
Marauder = 1 << 3,
|
|
||||||
Lancer = 1 << 4,
|
|
||||||
Archer = 1 << 5,
|
|
||||||
Conjurer = 1 << 6,
|
|
||||||
Thaumaturge = 1 << 7,
|
|
||||||
Paladin = 1 << 8,
|
|
||||||
Monk = 1 << 9,
|
|
||||||
Warrior = 1 << 10,
|
|
||||||
Dragoon = 1 << 11,
|
|
||||||
Bard = 1 << 12,
|
|
||||||
WhiteMage = 1 << 13,
|
|
||||||
BlackMage = 1 << 14,
|
|
||||||
Arcanist = 1 << 15,
|
|
||||||
Summoner = 1 << 16,
|
|
||||||
Scholar = 1 << 17,
|
|
||||||
Rogue = 1 << 18,
|
|
||||||
Ninja = 1 << 19,
|
|
||||||
Machinist = 1 << 20,
|
|
||||||
DarkKnight = 1 << 21,
|
|
||||||
Astrologian = 1 << 22,
|
|
||||||
Samurai = 1 << 23,
|
|
||||||
RedMage = 1 << 24,
|
|
||||||
BlueMage = 1 << 25,
|
|
||||||
Gunbreaker = 1 << 26,
|
|
||||||
Dancer = 1 << 27,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class JobFlagsExt {
|
|
||||||
/// <summary>
|
|
||||||
/// Get the actual ClassJob from the in-game sheets for this JobFlags.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="job">A JobFlags enum member</param>
|
|
||||||
/// <param name="data">A DataManager to get the ClassJob from</param>
|
|
||||||
/// <returns>A ClassJob if found or null if not</returns>
|
|
||||||
public static ClassJob ClassJob(this JobFlags job, DataManager data) {
|
|
||||||
var jobs = data.GetExcelSheet<ClassJob>();
|
|
||||||
|
|
||||||
uint? row = job switch {
|
|
||||||
JobFlags.Gladiator => 1,
|
|
||||||
JobFlags.Pugilist => 2,
|
|
||||||
JobFlags.Marauder => 3,
|
|
||||||
JobFlags.Lancer => 4,
|
|
||||||
JobFlags.Archer => 5,
|
|
||||||
JobFlags.Conjurer => 6,
|
|
||||||
JobFlags.Thaumaturge => 7,
|
|
||||||
JobFlags.Paladin => 19,
|
|
||||||
JobFlags.Monk => 20,
|
|
||||||
JobFlags.Warrior => 21,
|
|
||||||
JobFlags.Dragoon => 22,
|
|
||||||
JobFlags.Bard => 23,
|
|
||||||
JobFlags.WhiteMage => 24,
|
|
||||||
JobFlags.BlackMage => 25,
|
|
||||||
JobFlags.Arcanist => 26,
|
|
||||||
JobFlags.Summoner => 27,
|
|
||||||
JobFlags.Scholar => 28,
|
|
||||||
JobFlags.Rogue => 29,
|
|
||||||
JobFlags.Ninja => 30,
|
|
||||||
JobFlags.Machinist => 31,
|
|
||||||
JobFlags.DarkKnight => 32,
|
|
||||||
JobFlags.Astrologian => 33,
|
|
||||||
JobFlags.Samurai => 34,
|
|
||||||
JobFlags.RedMage => 35,
|
|
||||||
JobFlags.BlueMage => 36,
|
|
||||||
JobFlags.Gunbreaker => 37,
|
|
||||||
JobFlags.Dancer => 38,
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
return row == null ? null : jobs.GetRow((uint) row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum ObjectiveFlags : uint {
|
|
||||||
None = 0,
|
|
||||||
DutyCompletion = 1,
|
|
||||||
Practice = 2,
|
|
||||||
Loot = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum ConditionFlags : uint {
|
|
||||||
None = 1,
|
|
||||||
DutyComplete = 2,
|
|
||||||
DutyIncomplete = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum DutyFinderSettingsFlags : uint {
|
|
||||||
None = 0,
|
|
||||||
UndersizedParty = 1 << 0,
|
|
||||||
MinimumItemLevel = 1 << 1,
|
|
||||||
SilenceEcho = 1 << 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum LootRuleFlags : uint {
|
|
||||||
None = 0,
|
|
||||||
GreedOnly = 1,
|
|
||||||
Lootmaster = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Category {
|
|
||||||
Duty = 0,
|
|
||||||
QuestBattles = 1 << 0,
|
|
||||||
Fates = 1 << 1,
|
|
||||||
TreasureHunt = 1 << 2,
|
|
||||||
TheHunt = 1 << 3,
|
|
||||||
GatheringForays = 1 << 4,
|
|
||||||
DeepDungeons = 1 << 5,
|
|
||||||
AdventuringForays = 1 << 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DutyType {
|
|
||||||
Other = 0,
|
|
||||||
Roulette = 1 << 0,
|
|
||||||
Normal = 1 << 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
230
Dalamud/Game/Internal/Gui/Structs/PartyFinderListing.cs
Normal file
230
Dalamud/Game/Internal/Gui/Structs/PartyFinderListing.cs
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Internal.Gui.Structs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A single listing in party finder.
|
||||||
|
/// </summary>
|
||||||
|
public class PartyFinderListing
|
||||||
|
{
|
||||||
|
#region Backing fields
|
||||||
|
|
||||||
|
private readonly byte objective;
|
||||||
|
private readonly byte conditions;
|
||||||
|
private readonly byte dutyFinderSettings;
|
||||||
|
private readonly byte lootRules;
|
||||||
|
private readonly byte searchArea;
|
||||||
|
private readonly PartyFinderSlot[] slots;
|
||||||
|
private readonly byte[] jobsPresent;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PartyFinderListing"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listing">The interop listing data.</param>
|
||||||
|
/// <param name="dataManager">The DataManager instance.</param>
|
||||||
|
/// <param name="seStringManager">The SeStringManager instance.</param>
|
||||||
|
internal PartyFinderListing(PartyFinder.Listing listing, DataManager dataManager, SeStringManager seStringManager)
|
||||||
|
{
|
||||||
|
this.objective = listing.Objective;
|
||||||
|
this.conditions = listing.Conditions;
|
||||||
|
this.dutyFinderSettings = listing.DutyFinderSettings;
|
||||||
|
this.lootRules = listing.LootRules;
|
||||||
|
this.searchArea = listing.SearchArea;
|
||||||
|
this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray();
|
||||||
|
this.jobsPresent = listing.JobsPresent;
|
||||||
|
|
||||||
|
this.Id = listing.Id;
|
||||||
|
this.ContentIdLower = listing.ContentIdLower;
|
||||||
|
this.Name = seStringManager.Parse(listing.Name.TakeWhile(b => b != 0).ToArray());
|
||||||
|
this.Description = seStringManager.Parse(listing.Description.TakeWhile(b => b != 0).ToArray());
|
||||||
|
this.World = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.World));
|
||||||
|
this.HomeWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.HomeWorld));
|
||||||
|
this.CurrentWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.CurrentWorld));
|
||||||
|
this.Category = (Category)listing.Category;
|
||||||
|
this.RawDuty = listing.Duty;
|
||||||
|
this.Duty = new Lazy<ContentFinderCondition>(() => dataManager.GetExcelSheet<ContentFinderCondition>().GetRow(listing.Duty));
|
||||||
|
this.DutyType = (DutyType)listing.DutyType;
|
||||||
|
this.BeginnersWelcome = listing.BeginnersWelcome == 1;
|
||||||
|
this.SecondsRemaining = listing.SecondsRemaining;
|
||||||
|
this.MinimumItemLevel = listing.MinimumItemLevel;
|
||||||
|
this.Parties = listing.NumParties;
|
||||||
|
this.SlotsAvailable = listing.NumSlots;
|
||||||
|
this.JobsPresent = listing.JobsPresent
|
||||||
|
.Select(id => new Lazy<ClassJob>(
|
||||||
|
() => id == 0
|
||||||
|
? null
|
||||||
|
: dataManager.GetExcelSheet<ClassJob>().GetRow(id)))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID assigned to this listing by the game's server.
|
||||||
|
/// </summary>
|
||||||
|
public uint Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the lower bits of the player's content ID.
|
||||||
|
/// </summary>
|
||||||
|
public uint ContentIdLower { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the player hosting this listing.
|
||||||
|
/// </summary>
|
||||||
|
public SeString Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description of this listing as set by the host. May be multiple lines.
|
||||||
|
/// </summary>
|
||||||
|
public SeString Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the world that this listing was created on.
|
||||||
|
/// </summary>
|
||||||
|
public Lazy<World> World { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the home world of the listing's host.
|
||||||
|
/// </summary>
|
||||||
|
public Lazy<World> HomeWorld { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current world of the listing's host.
|
||||||
|
/// </summary>
|
||||||
|
public Lazy<World> CurrentWorld { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Party Finder category this listing is listed under.
|
||||||
|
/// </summary>
|
||||||
|
public Category Category { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings.
|
||||||
|
/// </summary>
|
||||||
|
public ushort RawDuty { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the duty this listing is for. May be null for non-duty listings.
|
||||||
|
/// </summary>
|
||||||
|
public Lazy<ContentFinderCondition> Duty { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of duty this listing is for.
|
||||||
|
/// </summary>
|
||||||
|
public DutyType DutyType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game.
|
||||||
|
/// </summary>
|
||||||
|
public bool BeginnersWelcome { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets how many seconds this listing will continue to be available for. It may end before this time if the party
|
||||||
|
/// fills or the host ends it early.
|
||||||
|
/// </summary>
|
||||||
|
public ushort SecondsRemaining { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the minimum item level required to join this listing.
|
||||||
|
/// </summary>
|
||||||
|
public ushort MinimumItemLevel { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of parties this listing is recruiting for.
|
||||||
|
/// </summary>
|
||||||
|
public byte Parties { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of player slots this listing is recruiting for.
|
||||||
|
/// </summary>
|
||||||
|
public byte SlotsAvailable { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of player slots that the Party Finder is accepting.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<PartyFinderSlot> Slots => this.slots;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the objective of this listing.
|
||||||
|
/// </summary>
|
||||||
|
public ObjectiveFlags Objective => (ObjectiveFlags)this.objective;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the conditions of this listing.
|
||||||
|
/// </summary>
|
||||||
|
public ConditionFlags Conditions => (ConditionFlags)this.conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Duty Finder settings that will be used for this listing.
|
||||||
|
/// </summary>
|
||||||
|
public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the loot rules that will be used for this listing.
|
||||||
|
/// </summary>
|
||||||
|
public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one
|
||||||
|
/// player per job.
|
||||||
|
/// </summary>
|
||||||
|
public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of the class/job IDs that are currently present in the party.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<byte> RawJobsPresent => this.jobsPresent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of the classes/jobs that are currently present in the party.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
|
||||||
|
|
||||||
|
#region Indexers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given flag is present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The flag to check for.</param>
|
||||||
|
/// <returns>A value indicating whether the flag is present.</returns>
|
||||||
|
public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given flag is present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The flag to check for.</param>
|
||||||
|
/// <returns>A value indicating whether the flag is present.</returns>
|
||||||
|
public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given flag is present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The flag to check for.</param>
|
||||||
|
/// <returns>A value indicating whether the flag is present.</returns>
|
||||||
|
public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given flag is present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The flag to check for.</param>
|
||||||
|
/// <returns>A value indicating whether the flag is present.</returns>
|
||||||
|
public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given flag is present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The flag to check for.</param>
|
||||||
|
/// <returns>A value indicating whether the flag is present.</returns>
|
||||||
|
public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Dalamud/Game/Internal/Gui/Structs/PartyFinderSlot.cs
Normal file
51
Dalamud/Game/Internal/Gui/Structs/PartyFinderSlot.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Internal.Gui.Structs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A player slot in a Party Finder listing.
|
||||||
|
/// </summary>
|
||||||
|
public class PartyFinderSlot
|
||||||
|
{
|
||||||
|
private readonly uint accepting;
|
||||||
|
private JobFlags[] listAccepting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PartyFinderSlot"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accepting">The flag value of accepted jobs.</param>
|
||||||
|
internal PartyFinderSlot(uint accepting)
|
||||||
|
{
|
||||||
|
this.accepting = accepting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of jobs that this slot is accepting.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<JobFlags> Accepting
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.listAccepting != null)
|
||||||
|
{
|
||||||
|
return this.listAccepting;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listAccepting = Enum.GetValues(typeof(JobFlags))
|
||||||
|
.Cast<JobFlags>()
|
||||||
|
.Where(flag => this[flag])
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return this.listAccepting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if this slot is accepting a job.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">Job to test.</param>
|
||||||
|
public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
397
Dalamud/Game/Internal/Gui/Structs/PartyFinderTypes.cs
Normal file
397
Dalamud/Game/Internal/Gui/Structs/PartyFinderTypes.cs
Normal file
|
|
@ -0,0 +1,397 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Internal.Gui.Structs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Search area flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum SearchAreaFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Datacenter.
|
||||||
|
/// </summary>
|
||||||
|
DataCentre = 1 << 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private.
|
||||||
|
/// </summary>
|
||||||
|
Private = 1 << 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Alliance raid.
|
||||||
|
/// </summary>
|
||||||
|
AllianceRaid = 1 << 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// World.
|
||||||
|
/// </summary>
|
||||||
|
World = 1 << 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One player per job.
|
||||||
|
/// </summary>
|
||||||
|
OnePlayerPerJob = 1 << 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Job flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum JobFlags
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gladiator (GLD).
|
||||||
|
/// </summary>
|
||||||
|
Gladiator = 1 << 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pugilist (PGL).
|
||||||
|
/// </summary>
|
||||||
|
Pugilist = 1 << 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marauder (MRD).
|
||||||
|
/// </summary>
|
||||||
|
Marauder = 1 << 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lancer (LNC).
|
||||||
|
/// </summary>
|
||||||
|
Lancer = 1 << 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Archer (ARC).
|
||||||
|
/// </summary>
|
||||||
|
Archer = 1 << 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Conjurer (CNJ).
|
||||||
|
/// </summary>
|
||||||
|
Conjurer = 1 << 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thaumaturge (THM).
|
||||||
|
/// </summary>
|
||||||
|
Thaumaturge = 1 << 7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Paladin (PLD).
|
||||||
|
/// </summary>
|
||||||
|
Paladin = 1 << 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Monk (MNK).
|
||||||
|
/// </summary>
|
||||||
|
Monk = 1 << 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Warrior (WAR).
|
||||||
|
/// </summary>
|
||||||
|
Warrior = 1 << 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dragoon (DRG).
|
||||||
|
/// </summary>
|
||||||
|
Dragoon = 1 << 11,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bard (BRD).
|
||||||
|
/// </summary>
|
||||||
|
Bard = 1 << 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// White mage (WHM).
|
||||||
|
/// </summary>
|
||||||
|
WhiteMage = 1 << 13,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Black mage (BLM).
|
||||||
|
/// </summary>
|
||||||
|
BlackMage = 1 << 14,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Arcanist (ACN).
|
||||||
|
/// </summary>
|
||||||
|
Arcanist = 1 << 15,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Summoner (SMN).
|
||||||
|
/// </summary>
|
||||||
|
Summoner = 1 << 16,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scholar (SCH).
|
||||||
|
/// </summary>
|
||||||
|
Scholar = 1 << 17,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rogue (ROG).
|
||||||
|
/// </summary>
|
||||||
|
Rogue = 1 << 18,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ninja (NIN).
|
||||||
|
/// </summary>
|
||||||
|
Ninja = 1 << 19,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Machinist (MCH).
|
||||||
|
/// </summary>
|
||||||
|
Machinist = 1 << 20,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dark Knight (DRK).
|
||||||
|
/// </summary>
|
||||||
|
DarkKnight = 1 << 21,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Astrologian (AST).
|
||||||
|
/// </summary>
|
||||||
|
Astrologian = 1 << 22,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Samurai (SAM).
|
||||||
|
/// </summary>
|
||||||
|
Samurai = 1 << 23,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Red mage (RDM).
|
||||||
|
/// </summary>
|
||||||
|
RedMage = 1 << 24,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blue mage (BLM).
|
||||||
|
/// </summary>
|
||||||
|
BlueMage = 1 << 25,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gunbreaker (GNB).
|
||||||
|
/// </summary>
|
||||||
|
Gunbreaker = 1 << 26,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dancer (DNC).
|
||||||
|
/// </summary>
|
||||||
|
Dancer = 1 << 27,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objective flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum ObjectiveFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No objective.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The duty completion objective.
|
||||||
|
/// </summary>
|
||||||
|
DutyCompletion = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The practice objective.
|
||||||
|
/// </summary>
|
||||||
|
Practice = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The loot objective.
|
||||||
|
/// </summary>
|
||||||
|
Loot = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum ConditionFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No duty condition.
|
||||||
|
/// </summary>
|
||||||
|
None = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The duty complete condition.
|
||||||
|
/// </summary>
|
||||||
|
DutyComplete = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The duty incomplete condition.
|
||||||
|
/// </summary>
|
||||||
|
DutyIncomplete = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duty finder settings flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum DutyFinderSettingsFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No duty finder settings.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The undersized party setting.
|
||||||
|
/// </summary>
|
||||||
|
UndersizedParty = 1 << 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum item level setting.
|
||||||
|
/// </summary>
|
||||||
|
MinimumItemLevel = 1 << 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The silence echo setting.
|
||||||
|
/// </summary>
|
||||||
|
SilenceEcho = 1 << 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loot rule flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum LootRuleFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No loot rules.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The greed only rule.
|
||||||
|
/// </summary>
|
||||||
|
GreedOnly = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The lootmaster rule.
|
||||||
|
/// </summary>
|
||||||
|
Lootmaster = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Category flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public enum Category
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The duty category.
|
||||||
|
/// </summary>
|
||||||
|
Duty = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The quest battle category.
|
||||||
|
/// </summary>
|
||||||
|
QuestBattles = 1 << 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fate category.
|
||||||
|
/// </summary>
|
||||||
|
Fates = 1 << 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The treasure hunt category.
|
||||||
|
/// </summary>
|
||||||
|
TreasureHunt = 1 << 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The hunt category.
|
||||||
|
/// </summary>
|
||||||
|
TheHunt = 1 << 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gathering forays category.
|
||||||
|
/// </summary>
|
||||||
|
GatheringForays = 1 << 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The deep dungeons category.
|
||||||
|
/// </summary>
|
||||||
|
DeepDungeons = 1 << 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The adventuring forays category.
|
||||||
|
/// </summary>
|
||||||
|
AdventuringForays = 1 << 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duty type flags for the <see cref="PartyFinder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public enum DutyType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No duty type.
|
||||||
|
/// </summary>
|
||||||
|
Other = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The roulette duty type.
|
||||||
|
/// </summary>
|
||||||
|
Roulette = 1 << 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The normal duty type.
|
||||||
|
/// </summary>
|
||||||
|
Normal = 1 << 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for the <see cref="JobFlags"/> enum.
|
||||||
|
/// </summary>
|
||||||
|
public static class JobFlagsExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the actual ClassJob from the in-game sheets for this JobFlags.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="job">A JobFlags enum member.</param>
|
||||||
|
/// <param name="data">A DataManager to get the ClassJob from.</param>
|
||||||
|
/// <returns>A ClassJob if found or null if not.</returns>
|
||||||
|
public static ClassJob ClassJob(this JobFlags job, DataManager data)
|
||||||
|
{
|
||||||
|
var jobs = data.GetExcelSheet<ClassJob>();
|
||||||
|
|
||||||
|
uint? row = job switch
|
||||||
|
{
|
||||||
|
JobFlags.Gladiator => 1,
|
||||||
|
JobFlags.Pugilist => 2,
|
||||||
|
JobFlags.Marauder => 3,
|
||||||
|
JobFlags.Lancer => 4,
|
||||||
|
JobFlags.Archer => 5,
|
||||||
|
JobFlags.Conjurer => 6,
|
||||||
|
JobFlags.Thaumaturge => 7,
|
||||||
|
JobFlags.Paladin => 19,
|
||||||
|
JobFlags.Monk => 20,
|
||||||
|
JobFlags.Warrior => 21,
|
||||||
|
JobFlags.Dragoon => 22,
|
||||||
|
JobFlags.Bard => 23,
|
||||||
|
JobFlags.WhiteMage => 24,
|
||||||
|
JobFlags.BlackMage => 25,
|
||||||
|
JobFlags.Arcanist => 26,
|
||||||
|
JobFlags.Summoner => 27,
|
||||||
|
JobFlags.Scholar => 28,
|
||||||
|
JobFlags.Rogue => 29,
|
||||||
|
JobFlags.Ninja => 30,
|
||||||
|
JobFlags.Machinist => 31,
|
||||||
|
JobFlags.DarkKnight => 32,
|
||||||
|
JobFlags.Astrologian => 33,
|
||||||
|
JobFlags.Samurai => 34,
|
||||||
|
JobFlags.RedMage => 35,
|
||||||
|
JobFlags.BlueMage => 36,
|
||||||
|
JobFlags.Gunbreaker => 37,
|
||||||
|
JobFlags.Dancer => 38,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return row == null ? null : jobs.GetRow((uint)row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
using Dalamud.Game.ClientState;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Game.ClientState.Structs.JobGauge;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Serilog;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
public class TargetManager {
|
|
||||||
public delegate IntPtr GetTargetDelegate(IntPtr manager);
|
|
||||||
|
|
||||||
private Hook<GetTargetDelegate> getTargetHook;
|
|
||||||
|
|
||||||
private TargetManagerAddressResolver Address;
|
|
||||||
|
|
||||||
public unsafe TargetManager(Dalamud dalamud, SigScanner scanner) {
|
|
||||||
this.Address = new TargetManagerAddressResolver();
|
|
||||||
this.Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("===== T A R G E T M A N A G E R =====");
|
|
||||||
Log.Verbose("GetTarget address {GetTarget}", Address.GetTarget);
|
|
||||||
|
|
||||||
this.getTargetHook = new Hook<GetTargetDelegate>(this.Address.GetTarget, new GetTargetDelegate(GetTargetDetour), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.getTargetHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.getTargetHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetTargetDetour(IntPtr manager)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
var res = this.getTargetHook.Original(manager);
|
|
||||||
|
|
||||||
var test = Marshal.ReadInt32(res);
|
|
||||||
|
|
||||||
Log.Debug($"GetTargetDetour {manager.ToInt64():X} -> RET: {res:X}");
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception GetTargetDetour hook.");
|
|
||||||
return this.getTargetHook.Original(manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
class TargetManagerAddressResolver : BaseAddressResolver {
|
|
||||||
public IntPtr GetTarget { get; private set; }
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
this.GetTarget = sig.ScanText("40 57 48 83 EC 40 48 8B F9 48 8B 49 08 48 8B 01 FF 50 40 66 83 B8 CA 81 00 00 00 74 33 48 8B 4F 08 48 8B 01 FF 50 40 66 83 B8 CA 81 00 00 04 74");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
namespace Dalamud.Game.Internal.Gui.Toast
|
namespace Dalamud.Game.Internal.Gui.Toast
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents options that can be used with the <see cref="ToastGui"/> class for the quest toast variant.
|
||||||
|
/// </summary>
|
||||||
public sealed class QuestToastOptions
|
public sealed class QuestToastOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -25,12 +28,5 @@
|
||||||
/// This only works if <see cref="IconId"/> is non-zero or <see cref="DisplayCheckmark"/> is true.
|
/// This only works if <see cref="IconId"/> is non-zero or <see cref="DisplayCheckmark"/> is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PlaySound { get; set; } = false;
|
public bool PlaySound { get; set; } = false;
|
||||||
|
|
||||||
internal (uint, uint) DetermineParameterOrder()
|
|
||||||
{
|
|
||||||
return this.DisplayCheckmark
|
|
||||||
? (ToastGui.QuestToastCheckmarkMagic, this.IconId)
|
|
||||||
: (this.IconId, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,23 @@
|
||||||
namespace Dalamud.Game.Internal.Gui.Toast
|
namespace Dalamud.Game.Internal.Gui.Toast
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The alignment of native quest toast windows.
|
||||||
|
/// </summary>
|
||||||
public enum QuestToastPosition
|
public enum QuestToastPosition
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The toast will be aligned screen centre.
|
||||||
|
/// </summary>
|
||||||
Centre = 0,
|
Centre = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The toast will be aligned screen right.
|
||||||
|
/// </summary>
|
||||||
Right = 1,
|
Right = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The toast will be aligned screen left.
|
||||||
|
/// </summary>
|
||||||
Left = 2,
|
Left = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
namespace Dalamud.Game.Internal.Gui.Toast
|
namespace Dalamud.Game.Internal.Gui.Toast
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents options that can be used with the <see cref="ToastGui"/> class.
|
||||||
|
/// </summary>
|
||||||
public sealed class ToastOptions
|
public sealed class ToastOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
namespace Dalamud.Game.Internal.Gui.Toast
|
namespace Dalamud.Game.Internal.Gui.Toast
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The positioning of native toast windows.
|
||||||
|
/// </summary>
|
||||||
public enum ToastPosition : byte
|
public enum ToastPosition : byte
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The toast will be towards the bottom.
|
||||||
|
/// </summary>
|
||||||
Bottom = 0,
|
Bottom = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The toast will be towards the top.
|
||||||
|
/// </summary>
|
||||||
Top = 1,
|
Top = 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
namespace Dalamud.Game.Internal.Gui.Toast
|
namespace Dalamud.Game.Internal.Gui.Toast
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The speed at which native toast windows will persist.
|
||||||
|
/// </summary>
|
||||||
public enum ToastSpeed : byte
|
public enum ToastSpeed : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
@ -8,18 +8,80 @@ using Dalamud.Hooking;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui
|
namespace Dalamud.Game.Internal.Gui
|
||||||
{
|
{
|
||||||
public sealed class ToastGui : IDisposable
|
/// <summary>
|
||||||
|
/// This class facilitates interacting with and creating native toast windows.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ToastGui : IDisposable
|
||||||
{
|
{
|
||||||
internal const uint QuestToastCheckmarkMagic = 60081;
|
private const uint QuestToastCheckmarkMagic = 60081;
|
||||||
|
|
||||||
#region Events
|
private readonly Dalamud dalamud;
|
||||||
|
private readonly ToastGuiAddressResolver address;
|
||||||
|
|
||||||
|
private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new();
|
||||||
|
private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new();
|
||||||
|
private readonly Queue<byte[]> errorQueue = new();
|
||||||
|
|
||||||
|
private readonly Hook<ShowNormalToastDelegate> showNormalToastHook;
|
||||||
|
private readonly Hook<ShowQuestToastDelegate> showQuestToastHook;
|
||||||
|
private readonly Hook<ShowErrorToastDelegate> showErrorToastHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ToastGui"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public ToastGui(SigScanner scanner, Dalamud dalamud)
|
||||||
|
{
|
||||||
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
|
this.address = new ToastGuiAddressResolver();
|
||||||
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
|
this.showNormalToastHook = new Hook<ShowNormalToastDelegate>(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
||||||
|
this.showQuestToastHook = new Hook<ShowQuestToastDelegate>(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
||||||
|
this.showErrorToastHook = new Hook<ShowErrorToastDelegate>(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Event delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used when a normal toast window appears.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message displayed.</param>
|
||||||
|
/// <param name="options">Assorted toast options.</param>
|
||||||
|
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||||
public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled);
|
public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used when a quest toast window appears.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message displayed.</param>
|
||||||
|
/// <param name="options">Assorted toast options.</param>
|
||||||
|
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||||
public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled);
|
public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used when an error toast window appears.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message displayed.</param>
|
||||||
|
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||||
public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled);
|
public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Marshal delegates
|
||||||
|
|
||||||
|
private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
|
||||||
|
|
||||||
|
private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
||||||
|
|
||||||
|
private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that will be fired when a toast is sent by the game or a plugin.
|
/// Event that will be fired when a toast is sent by the game or a plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -37,48 +99,9 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Hooks
|
/// <summary>
|
||||||
|
/// Enables this module.
|
||||||
private readonly Hook<ShowNormalToastDelegate> showNormalToastHook;
|
/// </summary>
|
||||||
|
|
||||||
private readonly Hook<ShowQuestToastDelegate> showQuestToastHook;
|
|
||||||
|
|
||||||
private readonly Hook<ShowErrorToastDelegate> showErrorToastHook;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Delegates
|
|
||||||
|
|
||||||
private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
|
|
||||||
|
|
||||||
private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
|
||||||
|
|
||||||
private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private Dalamud Dalamud { get; }
|
|
||||||
|
|
||||||
private ToastGuiAddressResolver Address { get; }
|
|
||||||
|
|
||||||
private Queue<(byte[], ToastOptions)> NormalQueue { get; } = new Queue<(byte[], ToastOptions)>();
|
|
||||||
|
|
||||||
private Queue<(byte[], QuestToastOptions)> QuestQueue { get; } = new Queue<(byte[], QuestToastOptions)>();
|
|
||||||
|
|
||||||
private Queue<byte[]> ErrorQueue { get; } = new Queue<byte[]>();
|
|
||||||
|
|
||||||
public ToastGui(SigScanner scanner, Dalamud dalamud)
|
|
||||||
{
|
|
||||||
this.Dalamud = dalamud;
|
|
||||||
|
|
||||||
this.Address = new ToastGuiAddressResolver();
|
|
||||||
this.Address.Setup(scanner);
|
|
||||||
|
|
||||||
this.showNormalToastHook = new Hook<ShowNormalToastDelegate>(this.Address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
|
||||||
this.showQuestToastHook = new Hook<ShowQuestToastDelegate>(this.Address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
|
||||||
this.showErrorToastHook = new Hook<ShowErrorToastDelegate>(this.Address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
this.showNormalToastHook.Enable();
|
this.showNormalToastHook.Enable();
|
||||||
|
|
@ -86,6 +109,9 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
this.showErrorToastHook.Enable();
|
this.showErrorToastHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.showNormalToastHook.Dispose();
|
this.showNormalToastHook.Dispose();
|
||||||
|
|
@ -93,6 +119,30 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
this.showErrorToastHook.Dispose();
|
this.showErrorToastHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process the toast queue.
|
||||||
|
/// </summary>
|
||||||
|
internal void UpdateQueue()
|
||||||
|
{
|
||||||
|
while (this.normalQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var (message, options) = this.normalQueue.Dequeue();
|
||||||
|
this.ShowNormal(message, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this.questQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var (message, options) = this.questQueue.Dequeue();
|
||||||
|
this.ShowQuest(message, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this.errorQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var message = this.errorQueue.Dequeue();
|
||||||
|
this.ShowError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] Terminate(byte[] source)
|
private static byte[] Terminate(byte[] source)
|
||||||
{
|
{
|
||||||
var terminated = new byte[source.Length + 1];
|
var terminated = new byte[source.Length + 1];
|
||||||
|
|
@ -107,7 +157,7 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
var bytes = new List<byte>();
|
var bytes = new List<byte>();
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
var ptr = (byte*) text;
|
var ptr = (byte*)text;
|
||||||
while (*ptr != 0)
|
while (*ptr != 0)
|
||||||
{
|
{
|
||||||
bytes.Add(*ptr);
|
bytes.Add(*ptr);
|
||||||
|
|
@ -116,62 +166,42 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
}
|
}
|
||||||
|
|
||||||
// call events
|
// call events
|
||||||
return this.Dalamud.SeStringManager.Parse(bytes.ToArray());
|
return this.dalamud.SeStringManager.Parse(bytes.ToArray());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process the toast queue.
|
/// Handles normal toasts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void UpdateQueue()
|
public sealed partial class ToastGui
|
||||||
{
|
{
|
||||||
while (this.NormalQueue.Count > 0)
|
|
||||||
{
|
|
||||||
var (message, options) = this.NormalQueue.Dequeue();
|
|
||||||
this.ShowNormal(message, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.QuestQueue.Count > 0)
|
|
||||||
{
|
|
||||||
var (message, options) = this.QuestQueue.Dequeue();
|
|
||||||
this.ShowQuest(message, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.ErrorQueue.Count > 0)
|
|
||||||
{
|
|
||||||
var message = this.ErrorQueue.Dequeue();
|
|
||||||
this.ShowError(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Normal API
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show a toast message with the given content.
|
/// Show a toast message with the given content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message to be shown</param>
|
/// <param name="message">The message to be shown.</param>
|
||||||
/// <param name="options">Options for the toast</param>
|
/// <param name="options">Options for the toast.</param>
|
||||||
public void ShowNormal(string message, ToastOptions options = null)
|
public void ShowNormal(string message, ToastOptions options = null)
|
||||||
{
|
{
|
||||||
options ??= new ToastOptions();
|
options ??= new ToastOptions();
|
||||||
this.NormalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show a toast message with the given content.
|
/// Show a toast message with the given content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message to be shown</param>
|
/// <param name="message">The message to be shown.</param>
|
||||||
/// <param name="options">Options for the toast</param>
|
/// <param name="options">Options for the toast.</param>
|
||||||
public void ShowNormal(SeString message, ToastOptions options = null)
|
public void ShowNormal(SeString message, ToastOptions options = null)
|
||||||
{
|
{
|
||||||
options ??= new ToastOptions();
|
options ??= new ToastOptions();
|
||||||
this.NormalQueue.Enqueue((message.Encode(), options));
|
this.normalQueue.Enqueue((message.Encode(), options));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowNormal(byte[] bytes, ToastOptions options = null)
|
private void ShowNormal(byte[] bytes, ToastOptions options = null)
|
||||||
{
|
{
|
||||||
options ??= new ToastOptions();
|
options ??= new ToastOptions();
|
||||||
|
|
||||||
var manager = this.Dalamud.Framework.Gui.GetUIModule();
|
var manager = this.dalamud.Framework.Gui.GetUIModule();
|
||||||
|
|
||||||
// terminate the string
|
// terminate the string
|
||||||
var terminated = Terminate(bytes);
|
var terminated = Terminate(bytes);
|
||||||
|
|
@ -180,104 +210,11 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = terminated)
|
fixed (byte* ptr = terminated)
|
||||||
{
|
{
|
||||||
this.HandleNormalToastDetour(manager, (IntPtr) ptr, 5, (byte) options.Position, (byte) options.Speed, 0);
|
this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Quest API
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Show a quest toast message with the given content.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The message to be shown</param>
|
|
||||||
/// <param name="options">Options for the toast</param>
|
|
||||||
public void ShowQuest(string message, QuestToastOptions options = null)
|
|
||||||
{
|
|
||||||
options ??= new QuestToastOptions();
|
|
||||||
this.QuestQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Show a quest toast message with the given content.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The message to be shown</param>
|
|
||||||
/// <param name="options">Options for the toast</param>
|
|
||||||
public void ShowQuest(SeString message, QuestToastOptions options = null)
|
|
||||||
{
|
|
||||||
options ??= new QuestToastOptions();
|
|
||||||
this.QuestQueue.Enqueue((message.Encode(), options));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowQuest(byte[] bytes, QuestToastOptions options = null)
|
|
||||||
{
|
|
||||||
options ??= new QuestToastOptions();
|
|
||||||
|
|
||||||
var manager = this.Dalamud.Framework.Gui.GetUIModule();
|
|
||||||
|
|
||||||
// terminate the string
|
|
||||||
var terminated = Terminate(bytes);
|
|
||||||
|
|
||||||
var (ioc1, ioc2) = options.DetermineParameterOrder();
|
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
fixed (byte* ptr = terminated)
|
|
||||||
{
|
|
||||||
this.HandleQuestToastDetour(
|
|
||||||
manager,
|
|
||||||
(int) options.Position,
|
|
||||||
(IntPtr) ptr,
|
|
||||||
ioc1,
|
|
||||||
options.PlaySound ? (byte) 1 : (byte) 0,
|
|
||||||
ioc2,
|
|
||||||
0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Error API
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Show an error toast message with the given content.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The message to be shown</param>
|
|
||||||
public void ShowError(string message)
|
|
||||||
{
|
|
||||||
this.ErrorQueue.Enqueue(Encoding.UTF8.GetBytes(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Show an error toast message with the given content.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The message to be shown</param>
|
|
||||||
public void ShowError(SeString message)
|
|
||||||
{
|
|
||||||
this.ErrorQueue.Enqueue(message.Encode());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowError(byte[] bytes)
|
|
||||||
{
|
|
||||||
var manager = this.Dalamud.Framework.Gui.GetUIModule();
|
|
||||||
|
|
||||||
// terminate the string
|
|
||||||
var terminated = Terminate(bytes);
|
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
fixed (byte* ptr = terminated)
|
|
||||||
{
|
|
||||||
this.HandleErrorToastDetour(manager, (IntPtr) ptr, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId)
|
private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId)
|
||||||
{
|
{
|
||||||
if (text == IntPtr.Zero)
|
if (text == IntPtr.Zero)
|
||||||
|
|
@ -290,8 +227,8 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
var str = this.ParseString(text);
|
var str = this.ParseString(text);
|
||||||
var options = new ToastOptions
|
var options = new ToastOptions
|
||||||
{
|
{
|
||||||
Position = (ToastPosition) isTop,
|
Position = (ToastPosition)isTop,
|
||||||
Speed = (ToastSpeed) isFast,
|
Speed = (ToastSpeed)isFast,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.OnToast?.Invoke(ref str, ref options, ref isHandled);
|
this.OnToast?.Invoke(ref str, ref options, ref isHandled);
|
||||||
|
|
@ -308,7 +245,62 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
{
|
{
|
||||||
fixed (byte* message = terminated)
|
fixed (byte* message = terminated)
|
||||||
{
|
{
|
||||||
return this.showNormalToastHook.Original(manager, (IntPtr) message, layer, (byte) options.Position, (byte) options.Speed, logMessageId);
|
return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles quest toasts.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ToastGui
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Show a quest toast message with the given content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to be shown.</param>
|
||||||
|
/// <param name="options">Options for the toast.</param>
|
||||||
|
public void ShowQuest(string message, QuestToastOptions options = null)
|
||||||
|
{
|
||||||
|
options ??= new QuestToastOptions();
|
||||||
|
this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show a quest toast message with the given content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to be shown.</param>
|
||||||
|
/// <param name="options">Options for the toast.</param>
|
||||||
|
public void ShowQuest(SeString message, QuestToastOptions options = null)
|
||||||
|
{
|
||||||
|
options ??= new QuestToastOptions();
|
||||||
|
this.questQueue.Enqueue((message.Encode(), options));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowQuest(byte[] bytes, QuestToastOptions options = null)
|
||||||
|
{
|
||||||
|
options ??= new QuestToastOptions();
|
||||||
|
|
||||||
|
var manager = this.dalamud.Framework.Gui.GetUIModule();
|
||||||
|
|
||||||
|
// terminate the string
|
||||||
|
var terminated = Terminate(bytes);
|
||||||
|
|
||||||
|
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* ptr = terminated)
|
||||||
|
{
|
||||||
|
this.HandleQuestToastDetour(
|
||||||
|
manager,
|
||||||
|
(int)options.Position,
|
||||||
|
(IntPtr)ptr,
|
||||||
|
ioc1,
|
||||||
|
options.PlaySound ? (byte)1 : (byte)0,
|
||||||
|
ioc2,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -325,7 +317,7 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
var str = this.ParseString(text);
|
var str = this.ParseString(text);
|
||||||
var options = new QuestToastOptions
|
var options = new QuestToastOptions
|
||||||
{
|
{
|
||||||
Position = (QuestToastPosition) position,
|
Position = (QuestToastPosition)position,
|
||||||
DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic,
|
DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic,
|
||||||
IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1,
|
IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1,
|
||||||
PlaySound = playSound == 1,
|
PlaySound = playSound == 1,
|
||||||
|
|
@ -341,7 +333,7 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
|
|
||||||
var terminated = Terminate(str.Encode());
|
var terminated = Terminate(str.Encode());
|
||||||
|
|
||||||
var (ioc1, ioc2) = options.DetermineParameterOrder();
|
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|
@ -349,16 +341,63 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
{
|
{
|
||||||
return this.showQuestToastHook.Original(
|
return this.showQuestToastHook.Original(
|
||||||
manager,
|
manager,
|
||||||
(int) options.Position,
|
(int)options.Position,
|
||||||
(IntPtr) message,
|
(IntPtr)message,
|
||||||
ioc1,
|
ioc1,
|
||||||
options.PlaySound ? (byte) 1 : (byte) 0,
|
options.PlaySound ? (byte)1 : (byte)0,
|
||||||
ioc2,
|
ioc2,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options)
|
||||||
|
{
|
||||||
|
return options.DisplayCheckmark
|
||||||
|
? (QuestToastCheckmarkMagic, options.IconId)
|
||||||
|
: (options.IconId, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles error toasts.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ToastGui
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Show an error toast message with the given content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to be shown.</param>
|
||||||
|
public void ShowError(string message)
|
||||||
|
{
|
||||||
|
this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show an error toast message with the given content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to be shown.</param>
|
||||||
|
public void ShowError(SeString message)
|
||||||
|
{
|
||||||
|
this.errorQueue.Enqueue(message.Encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowError(byte[] bytes)
|
||||||
|
{
|
||||||
|
var manager = this.dalamud.Framework.Gui.GetUIModule();
|
||||||
|
|
||||||
|
// terminate the string
|
||||||
|
var terminated = Terminate(bytes);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* ptr = terminated)
|
||||||
|
{
|
||||||
|
this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe)
|
private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe)
|
||||||
{
|
{
|
||||||
if (text == IntPtr.Zero)
|
if (text == IntPtr.Zero)
|
||||||
|
|
@ -384,7 +423,7 @@ namespace Dalamud.Game.Internal.Gui
|
||||||
{
|
{
|
||||||
fixed (byte* message = terminated)
|
fixed (byte* message = terminated)
|
||||||
{
|
{
|
||||||
return this.showErrorToastHook.Original(manager, (IntPtr) message, respectsHidingMaybe);
|
return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,28 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui
|
namespace Dalamud.Game.Internal.Gui
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An address resolver for the <see cref="ToastGui"/> class.
|
||||||
|
/// </summary>
|
||||||
public class ToastGuiAddressResolver : BaseAddressResolver
|
public class ToastGuiAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ShowNormalToast method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ShowNormalToast { get; private set; }
|
public IntPtr ShowNormalToast { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ShowQuestToast method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ShowQuestToast { get; private set; }
|
public IntPtr ShowQuestToast { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the ShowErrorToast method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ShowErrorToast { get; private set; }
|
public IntPtr ShowErrorToast { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??");
|
this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??");
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,65 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
namespace Dalamud.Game.Internal.Libc
|
||||||
public sealed class LibcFunction {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class handles creating cstrings utilizing native game methods.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LibcFunction
|
||||||
|
{
|
||||||
|
private readonly LibcFunctionAddressResolver address;
|
||||||
|
private readonly StdStringFromCStringDelegate stdStringCtorCString;
|
||||||
|
private readonly StdStringDeallocateDelegate stdStringDeallocate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="LibcFunction"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
public LibcFunction(SigScanner scanner)
|
||||||
|
{
|
||||||
|
this.address = new LibcFunctionAddressResolver();
|
||||||
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
|
this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(this.address.StdStringFromCstring);
|
||||||
|
this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(this.address.StdStringDeallocate);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: prolly callconv is not okay in x86
|
// TODO: prolly callconv is not okay in x86
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)]byte[] content, IntPtr size);
|
private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)] byte[] content, IntPtr size);
|
||||||
|
|
||||||
// TODO: prolly callconv is not okay in x86
|
// TODO: prolly callconv is not okay in x86
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate IntPtr StdStringDeallocateDelegate(IntPtr address);
|
private delegate IntPtr StdStringDeallocateDelegate(IntPtr address);
|
||||||
|
|
||||||
private LibcFunctionAddressResolver Address { get; }
|
|
||||||
|
|
||||||
private readonly StdStringFromCStringDelegate stdStringCtorCString;
|
/// <summary>
|
||||||
private readonly StdStringDeallocateDelegate stdStringDeallocate;
|
/// Create a new string from the given bytes.
|
||||||
|
/// </summary>
|
||||||
public LibcFunction(SigScanner scanner) {
|
/// <param name="content">The bytes to convert.</param>
|
||||||
Address = new LibcFunctionAddressResolver();
|
/// <returns>An owned std string object.</returns>
|
||||||
Address.Setup(scanner);
|
public OwnedStdString NewString(byte[] content)
|
||||||
|
{
|
||||||
this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(Address.StdStringFromCstring);
|
|
||||||
this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(Address.StdStringDeallocate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OwnedStdString NewString(byte[] content) {
|
|
||||||
// While 0x70 bytes in the memory should be enough in DX11 version,
|
// While 0x70 bytes in the memory should be enough in DX11 version,
|
||||||
// I don't trust my analysis so we're just going to allocate almost two times more than that.
|
// I don't trust my analysis so we're just going to allocate almost two times more than that.
|
||||||
var pString = Marshal.AllocHGlobal(256);
|
var pString = Marshal.AllocHGlobal(256);
|
||||||
|
|
||||||
// Initialize a string
|
// Initialize a string
|
||||||
var size = new IntPtr(content.Length);
|
var size = new IntPtr(content.Length);
|
||||||
var pReallocString = this.stdStringCtorCString(pString, content, size);
|
var pReallocString = this.stdStringCtorCString(pString, content, size);
|
||||||
|
|
||||||
//Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString);
|
// Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString);
|
||||||
|
|
||||||
return new OwnedStdString(pReallocString, DeallocateStdString);
|
return new OwnedStdString(pReallocString, this.DeallocateStdString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new string form the given bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">The bytes to convert.</param>
|
||||||
|
/// <param name="encoding">A non-default encoding.</param>
|
||||||
|
/// <returns>An owned std string object.</returns>
|
||||||
public OwnedStdString NewString(string content, Encoding encoding = null)
|
public OwnedStdString NewString(string content, Encoding encoding = null)
|
||||||
{
|
{
|
||||||
encoding ??= Encoding.UTF8;
|
encoding ??= Encoding.UTF8;
|
||||||
|
|
@ -47,7 +67,8 @@ namespace Dalamud.Game.Internal.Libc {
|
||||||
return this.NewString(encoding.GetBytes(content));
|
return this.NewString(encoding.GetBytes(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeallocateStdString(IntPtr address) {
|
private void DeallocateStdString(IntPtr address)
|
||||||
|
{
|
||||||
this.stdStringDeallocate(address);
|
this.stdStringDeallocate(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Policy;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
namespace Dalamud.Game.Internal.Libc
|
||||||
public sealed class LibcFunctionAddressResolver : BaseAddressResolver {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="LibcFunction"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LibcFunctionAddressResolver : BaseAddressResolver
|
||||||
|
{
|
||||||
private delegate IntPtr StringFromCString();
|
private delegate IntPtr StringFromCString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native StdStringFromCstring method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr StdStringFromCstring { get; private set; }
|
public IntPtr StdStringFromCstring { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native StdStringDeallocate method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr StdStringDeallocate { get; private set; }
|
public IntPtr StdStringDeallocate { get; private set; }
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
/// <inheritdoc/>
|
||||||
StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
|
{
|
||||||
|
this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
|
||||||
|
this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
namespace Dalamud.Game.Internal.Libc
|
||||||
public sealed class OwnedStdString : IDisposable {
|
{
|
||||||
internal delegate void DeallocatorDelegate(IntPtr address);
|
/// <summary>
|
||||||
|
/// An address wrapper around the <see cref="StdString"/> class.
|
||||||
// ala. the drop flag
|
/// </summary>
|
||||||
private bool isDisposed;
|
public sealed partial class OwnedStdString
|
||||||
|
{
|
||||||
private readonly DeallocatorDelegate dealloc;
|
private readonly DeallocatorDelegate dealloc;
|
||||||
|
|
||||||
public IntPtr Address { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a wrapper around std::string
|
/// Initializes a new instance of the <see cref="OwnedStdString"/> class.
|
||||||
|
/// Construct a wrapper around std::string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Violating any of these might cause an undefined hehaviour.
|
/// Violating any of these might cause an undefined hehaviour.
|
||||||
|
|
@ -22,47 +20,82 @@ namespace Dalamud.Game.Internal.Libc {
|
||||||
/// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address.
|
/// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address.
|
||||||
/// 3. std::string object pointed by address must be initialized before calling this function.
|
/// 3. std::string object pointed by address must be initialized before calling this function.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="address"></param>
|
/// <param name="address">The address of the owned std string.</param>
|
||||||
/// <param name="dealloc">A deallocator function.</param>
|
/// <param name="dealloc">A deallocator function.</param>
|
||||||
/// <returns></returns>
|
internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc)
|
||||||
internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) {
|
{
|
||||||
Address = address;
|
this.Address = address;
|
||||||
this.dealloc = dealloc;
|
this.dealloc = dealloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
~OwnedStdString() {
|
/// <summary>
|
||||||
ReleaseUnmanagedResources();
|
/// The delegate type that deallocates a std string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Address to deallocate.</param>
|
||||||
|
internal delegate void DeallocatorDelegate(IntPtr address);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the std string.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Address { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the wrapped StdString.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The StdString.</returns>
|
||||||
|
public StdString Read() => StdString.ReadFromPointer(this.Address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements IDisposable.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class OwnedStdString : IDisposable
|
||||||
|
{
|
||||||
|
private bool isDisposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes an instance of the <see cref="OwnedStdString"/> class.
|
||||||
|
/// </summary>
|
||||||
|
~OwnedStdString() => this.Dispose(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
this.Dispose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReleaseUnmanagedResources() {
|
/// <summary>
|
||||||
if (Address == IntPtr.Zero) {
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">A value indicating whether this was called via Dispose or finalized.</param>
|
||||||
|
public void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (this.isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.isDisposed = true;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Address == IntPtr.Zero)
|
||||||
|
{
|
||||||
// Something got seriously fucked.
|
// Something got seriously fucked.
|
||||||
throw new AccessViolationException();
|
throw new AccessViolationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deallocate inner string first
|
// Deallocate inner string first
|
||||||
this.dealloc(Address);
|
this.dealloc(this.Address);
|
||||||
|
|
||||||
// Free the heap
|
// Free the heap
|
||||||
Marshal.FreeHGlobal(Address);
|
Marshal.FreeHGlobal(this.Address);
|
||||||
|
|
||||||
// Better safe (running on a nullptr) than sorry. (running on a dangling pointer)
|
// Better safe (running on a nullptr) than sorry. (running on a dangling pointer)
|
||||||
Address = IntPtr.Zero;
|
this.Address = IntPtr.Zero;
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
// No double free plz, kthx.
|
|
||||||
if (this.isDisposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.isDisposed = true;
|
|
||||||
|
|
||||||
ReleaseUnmanagedResources();
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StdString Read() {
|
|
||||||
return StdString.ReadFromPointer(Address);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,56 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
namespace Dalamud.Game.Internal.Libc
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interation with std::string
|
/// Interation with std::string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StdString {
|
public class StdString
|
||||||
public static StdString ReadFromPointer(IntPtr cstring) {
|
{
|
||||||
unsafe {
|
/// <summary>
|
||||||
if (cstring == IntPtr.Zero) {
|
/// Initializes a new instance of the <see cref="StdString"/> class.
|
||||||
|
/// </summary>
|
||||||
|
private StdString()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the cstring.
|
||||||
|
/// </summary>
|
||||||
|
public string Value { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the raw byte representation of the cstring.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] RawData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marshal a null terminated cstring from memory to a UTF-8 encoded string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cstring">Address of the cstring.</param>
|
||||||
|
/// <returns>A UTF-8 encoded string.</returns>
|
||||||
|
public static StdString ReadFromPointer(IntPtr cstring)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (cstring == IntPtr.Zero)
|
||||||
|
{
|
||||||
throw new ArgumentNullException(nameof(cstring));
|
throw new ArgumentNullException(nameof(cstring));
|
||||||
}
|
}
|
||||||
|
|
||||||
var innerAddress = Marshal.ReadIntPtr(cstring);
|
var innerAddress = Marshal.ReadIntPtr(cstring);
|
||||||
if (innerAddress == IntPtr.Zero) {
|
if (innerAddress == IntPtr.Zero)
|
||||||
|
{
|
||||||
throw new NullReferenceException("Inner reference to the cstring is null.");
|
throw new NullReferenceException("Inner reference to the cstring is null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
// Count the number of chars. String is assumed to be zero-terminated.
|
// Count the number of chars. String is assumed to be zero-terminated.
|
||||||
while (Marshal.ReadByte(innerAddress + count) != 0) {
|
|
||||||
|
var count = 0;
|
||||||
|
while (Marshal.ReadByte(innerAddress, count) != 0)
|
||||||
|
{
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,17 +58,12 @@ namespace Dalamud.Game.Internal.Libc {
|
||||||
var rawData = new byte[count];
|
var rawData = new byte[count];
|
||||||
Marshal.Copy(innerAddress, rawData, 0, count);
|
Marshal.Copy(innerAddress, rawData, 0, count);
|
||||||
|
|
||||||
return new StdString {
|
return new StdString
|
||||||
|
{
|
||||||
RawData = rawData,
|
RawData = rawData,
|
||||||
Value = Encoding.UTF8.GetString(rawData)
|
Value = Encoding.UTF8.GetString(rawData),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StdString() { }
|
|
||||||
|
|
||||||
public string Value { get; private set; }
|
|
||||||
|
|
||||||
public byte[] RawData { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,128 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SharpDX.DXGI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Network {
|
|
||||||
public sealed class GameNetwork : IDisposable {
|
|
||||||
#region Hooks
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
|
|
||||||
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
|
|
||||||
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private GameNetworkAddressResolver Address { get; }
|
|
||||||
private IntPtr baseAddress;
|
|
||||||
|
|
||||||
public delegate void OnNetworkMessageDelegate(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction);
|
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Internal.Network
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class handles interacting with game network events.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GameNetwork : IDisposable
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that is called when a network message is sent/received.
|
/// Event that is called when a network message is sent/received.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public OnNetworkMessageDelegate OnNetworkMessage;
|
public OnNetworkMessageDelegate OnNetworkMessage;
|
||||||
|
|
||||||
|
private readonly GameNetworkAddressResolver address;
|
||||||
|
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
|
||||||
|
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
|
||||||
|
private readonly Queue<byte[]> zoneInjectQueue = new();
|
||||||
|
|
||||||
|
private IntPtr baseAddress;
|
||||||
|
|
||||||
private readonly Queue<byte[]> zoneInjectQueue = new Queue<byte[]>();
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameNetwork"/> class.
|
||||||
public GameNetwork(SigScanner scanner) {
|
/// </summary>
|
||||||
Address = new GameNetworkAddressResolver();
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
Address.Setup(scanner);
|
public GameNetwork(SigScanner scanner)
|
||||||
|
{
|
||||||
|
this.address = new GameNetworkAddressResolver();
|
||||||
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
Log.Verbose("===== G A M E N E T W O R K =====");
|
Log.Verbose("===== G A M E N E T W O R K =====");
|
||||||
Log.Verbose("ProcessZonePacketDown address {ProcessZonePacketDown}", Address.ProcessZonePacketDown);
|
Log.Verbose("ProcessZonePacketDown address {ProcessZonePacketDown}", this.address.ProcessZonePacketDown);
|
||||||
Log.Verbose("ProcessZonePacketUp address {ProcessZonePacketUp}", Address.ProcessZonePacketUp);
|
Log.Verbose("ProcessZonePacketUp address {ProcessZonePacketUp}", this.address.ProcessZonePacketUp);
|
||||||
|
|
||||||
this.processZonePacketDownHook =
|
this.processZonePacketDownHook = new Hook<ProcessZonePacketDownDelegate>(this.address.ProcessZonePacketDown, new ProcessZonePacketDownDelegate(this.ProcessZonePacketDownDetour), this);
|
||||||
new Hook<ProcessZonePacketDownDelegate>(Address.ProcessZonePacketDown,
|
|
||||||
new ProcessZonePacketDownDelegate(ProcessZonePacketDownDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
this.processZonePacketUpHook =
|
this.processZonePacketUpHook = new Hook<ProcessZonePacketUpDelegate>(this.address.ProcessZonePacketUp, new ProcessZonePacketUpDelegate(this.ProcessZonePacketUpDetour), this);
|
||||||
new Hook<ProcessZonePacketUpDelegate>(Address.ProcessZonePacketUp,
|
|
||||||
new ProcessZonePacketUpDelegate(ProcessZonePacketUpDetour),
|
|
||||||
this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enable() {
|
/// <summary>
|
||||||
|
/// The delegate type of a network message event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataPtr">The pointer to the raw data.</param>
|
||||||
|
/// <param name="opCode">The operation ID code.</param>
|
||||||
|
/// <param name="sourceActorId">The source actor ID.</param>
|
||||||
|
/// <param name="targetActorId">The taret actor ID.</param>
|
||||||
|
/// <param name="direction">The direction of the packed.</param>
|
||||||
|
public delegate void OnNetworkMessageDelegate(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable this module.
|
||||||
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
this.processZonePacketDownHook.Enable();
|
this.processZonePacketDownHook.Enable();
|
||||||
this.processZonePacketUpHook.Enable();
|
this.processZonePacketUpHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
this.processZonePacketDownHook.Dispose();
|
this.processZonePacketDownHook.Dispose();
|
||||||
this.processZonePacketUpHook.Dispose();
|
this.processZonePacketUpHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) {
|
/// <summary>
|
||||||
|
/// Process a chat queue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="framework">The Framework instance.</param>
|
||||||
|
public void UpdateQueue(Framework framework)
|
||||||
|
{
|
||||||
|
while (this.zoneInjectQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var packetData = this.zoneInjectQueue.Dequeue();
|
||||||
|
|
||||||
|
var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length);
|
||||||
|
Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length);
|
||||||
|
|
||||||
|
if (this.baseAddress != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(unmanagedPacketData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr)
|
||||||
|
{
|
||||||
this.baseAddress = a;
|
this.baseAddress = a;
|
||||||
|
|
||||||
// Go back 0x10 to get back to the start of the packet header
|
// Go back 0x10 to get back to the start of the packet header
|
||||||
dataPtr -= 0x10;
|
dataPtr -= 0x10;
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
|
|
||||||
// Call events
|
// Call events
|
||||||
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort) Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
|
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
|
||||||
|
|
||||||
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10);
|
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10);
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
string header;
|
string header;
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
var data = new byte[32];
|
var data = new byte[32];
|
||||||
Marshal.Copy(dataPtr, data, 0, 32);
|
Marshal.Copy(dataPtr, data, 0, 32);
|
||||||
header = BitConverter.ToString(data);
|
header = BitConverter.ToString(data);
|
||||||
} catch (Exception) {
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
header = "failed";
|
header = "failed";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,13 +132,13 @@ namespace Dalamud.Game.Internal.Network {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) {
|
private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Call events
|
// Call events
|
||||||
// TODO: Implement actor IDs
|
// TODO: Implement actor IDs
|
||||||
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort) Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp);
|
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -121,43 +161,26 @@ namespace Dalamud.Game.Internal.Network {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public void InjectZoneProtoPacket(byte[] data) {
|
private void InjectZoneProtoPacket(byte[] data)
|
||||||
|
{
|
||||||
this.zoneInjectQueue.Enqueue(data);
|
this.zoneInjectQueue.Enqueue(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InjectActorControl(short cat, int param1) {
|
private void InjectActorControl(short cat, int param1)
|
||||||
var packetData = new byte[] {
|
{
|
||||||
|
var packetData = new byte[]
|
||||||
|
{
|
||||||
0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00,
|
0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00,
|
||||||
};
|
};
|
||||||
|
|
||||||
BitConverter.GetBytes((short) cat).CopyTo(packetData, 0x10);
|
BitConverter.GetBytes((short)cat).CopyTo(packetData, 0x10);
|
||||||
|
|
||||||
BitConverter.GetBytes((UInt32) param1).CopyTo(packetData, 0x14);
|
BitConverter.GetBytes((uint)param1).CopyTo(packetData, 0x14);
|
||||||
|
|
||||||
InjectZoneProtoPacket(packetData);
|
this.InjectZoneProtoPacket(packetData);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process a chat queue.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateQueue(Framework framework)
|
|
||||||
{
|
|
||||||
while (this.zoneInjectQueue.Count > 0)
|
|
||||||
{
|
|
||||||
var packetData = this.zoneInjectQueue.Dequeue();
|
|
||||||
|
|
||||||
var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length);
|
|
||||||
Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length);
|
|
||||||
|
|
||||||
if (this.baseAddress != IntPtr.Zero) {
|
|
||||||
this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData);
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(unmanagedPacketData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Network {
|
namespace Dalamud.Game.Internal.Network
|
||||||
public sealed class GameNetworkAddressResolver : BaseAddressResolver {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="GameNetwork"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GameNetworkAddressResolver : BaseAddressResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the ProcessZonePacketDown method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ProcessZonePacketDown { get; private set; }
|
public IntPtr ProcessZonePacketDown { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the ProcessZonePacketUp method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr ProcessZonePacketUp { get; private set; }
|
public IntPtr ProcessZonePacketUp { get; private set; }
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
/// <inheritdoc/>
|
||||||
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
|
{
|
||||||
ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2");
|
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||||
ProcessZonePacketUp =
|
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||||
sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
|
this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2");
|
||||||
|
this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,18 @@
|
||||||
namespace Dalamud.Game.Internal.Network {
|
namespace Dalamud.Game.Internal.Network
|
||||||
public enum NetworkMessageDirection {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This represents the direction of a network message.
|
||||||
|
/// </summary>
|
||||||
|
public enum NetworkMessageDirection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A zone down message.
|
||||||
|
/// </summary>
|
||||||
ZoneDown,
|
ZoneDown,
|
||||||
ZoneUp
|
|
||||||
|
/// <summary>
|
||||||
|
/// A zone up message.
|
||||||
|
/// </summary>
|
||||||
|
ZoneUp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,135 +1,148 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.File
|
namespace Dalamud.Game.Internal.File
|
||||||
{
|
{
|
||||||
public class ResourceManager {
|
/// <summary>
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
/// This class facilitates modifying how the game loads resources from disk.
|
||||||
private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7);
|
/// </summary>
|
||||||
|
public class ResourceManager
|
||||||
|
{
|
||||||
|
private readonly Dalamud dalamud;
|
||||||
|
private readonly ResourceManagerAddressResolver address;
|
||||||
private readonly Hook<GetResourceAsyncDelegate> getResourceAsyncHook;
|
private readonly Hook<GetResourceAsyncDelegate> getResourceAsyncHook;
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr GetResourceSyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6);
|
|
||||||
private readonly Hook<GetResourceSyncDelegate> getResourceSyncHook;
|
private readonly Hook<GetResourceSyncDelegate> getResourceSyncHook;
|
||||||
|
|
||||||
private ResourceManagerAddressResolver Address { get; }
|
private Dictionary<IntPtr, ResourceHandleHookInfo> resourceHookMap = new();
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
class ResourceHandleHookInfo {
|
/// <summary>
|
||||||
public string Path { get; set; }
|
/// Initializes a new instance of the <see cref="ResourceManager"/> class.
|
||||||
public Stream DetourFile { get; set; }
|
/// </summary>
|
||||||
}
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
private Dictionary<IntPtr, ResourceHandleHookInfo> resourceHookMap = new Dictionary<IntPtr, ResourceHandleHookInfo>();
|
public ResourceManager(Dalamud dalamud, SigScanner scanner)
|
||||||
|
{
|
||||||
public ResourceManager(Dalamud dalamud, SigScanner scanner) {
|
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
Address = new ResourceManagerAddressResolver();
|
this.address = new ResourceManagerAddressResolver();
|
||||||
Address.Setup(scanner);
|
this.address.Setup(scanner);
|
||||||
|
|
||||||
Log.Verbose("===== R E S O U R C E M A N A G E R =====");
|
Log.Verbose("===== R E S O U R C E M A N A G E R =====");
|
||||||
Log.Verbose("GetResourceAsync address {GetResourceAsync}", Address.GetResourceAsync);
|
Log.Verbose("GetResourceAsync address {GetResourceAsync}", this.address.GetResourceAsync);
|
||||||
Log.Verbose("GetResourceSync address {GetResourceSync}", Address.GetResourceSync);
|
Log.Verbose("GetResourceSync address {GetResourceSync}", this.address.GetResourceSync);
|
||||||
|
|
||||||
this.getResourceAsyncHook =
|
this.getResourceAsyncHook = new Hook<GetResourceAsyncDelegate>(this.address.GetResourceAsync, new GetResourceAsyncDelegate(this.GetResourceAsyncDetour), this);
|
||||||
new Hook<GetResourceAsyncDelegate>(Address.GetResourceAsync,
|
|
||||||
new GetResourceAsyncDelegate(GetResourceAsyncDetour),
|
this.getResourceSyncHook = new Hook<GetResourceSyncDelegate>(this.address.GetResourceSync, new GetResourceSyncDelegate(this.GetResourceSyncDetour), this);
|
||||||
this);
|
|
||||||
|
|
||||||
this.getResourceSyncHook =
|
|
||||||
new Hook<GetResourceSyncDelegate>(Address.GetResourceSync,
|
|
||||||
new GetResourceSyncDelegate(GetResourceSyncDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enable() {
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6, byte a7);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr GetResourceSyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if a filepath has any invalid characters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The filepath to check.</param>
|
||||||
|
/// <returns>A value indicating whether the filepath is safe to use.</returns>
|
||||||
|
public static bool FilePathHasInvalidChars(string path)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(path) && path.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable this module.
|
||||||
|
/// </summary>
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
this.getResourceAsyncHook.Enable();
|
this.getResourceAsyncHook.Enable();
|
||||||
this.getResourceSyncHook.Enable();
|
this.getResourceSyncHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
this.getResourceAsyncHook.Dispose();
|
this.getResourceAsyncHook.Dispose();
|
||||||
this.getResourceSyncHook.Dispose();
|
this.getResourceSyncHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7) {
|
|
||||||
|
|
||||||
try {
|
private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6, byte a7)
|
||||||
var path = Marshal.PtrToStringAnsi(a5);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = Marshal.PtrToStringAnsi(pathPtr);
|
||||||
|
|
||||||
var resourceHandle = this.getResourceAsyncHook.Original(manager, a2, a3, a4, IntPtr.Zero, a6, a7);
|
var resourceHandle = this.getResourceAsyncHook.Original(manager, a2, a3, a4, IntPtr.Zero, a6, a7);
|
||||||
//var resourceHandle = IntPtr.Zero;
|
// var resourceHandle = IntPtr.Zero;
|
||||||
|
|
||||||
Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, a5, a6, a7, resourceHandle);
|
Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, pathPtr, a6, a7, resourceHandle);
|
||||||
|
|
||||||
Log.Verbose($"->{path}");
|
Log.Verbose($"->{path}");
|
||||||
|
|
||||||
HandleGetResourceHookAcquire(resourceHandle, path);
|
this.HandleGetResourceHookAcquire(resourceHandle, path);
|
||||||
|
|
||||||
return resourceHandle;
|
return resourceHandle;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Exception on ReadResourceAsync hook.");
|
Log.Error(ex, "Exception on ReadResourceAsync hook.");
|
||||||
|
|
||||||
return this.getResourceAsyncHook.Original(manager, a2, a3, a4, a5, a6, a7);
|
return this.getResourceAsyncHook.Original(manager, a2, a3, a4, pathPtr, a6, a7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DumpMem(IntPtr address, int len = 512) {
|
private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6)
|
||||||
if (address == IntPtr.Zero)
|
{
|
||||||
return;
|
try
|
||||||
|
{
|
||||||
|
var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, pathPtr, a6);
|
||||||
|
|
||||||
var data = new byte[len];
|
Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, pathPtr, a6, resourceHandle);
|
||||||
Marshal.Copy(address, data, 0, len);
|
|
||||||
|
|
||||||
Log.Verbose($"MEMDMP at {address.ToInt64():X} for {len:X}\n{Util.ByteArrayToHex(data)}");
|
var path = Marshal.PtrToStringAnsi(pathPtr);
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
|
|
||||||
|
|
||||||
Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, a5, a6, resourceHandle);
|
|
||||||
|
|
||||||
var path = Marshal.PtrToStringAnsi(a5);
|
|
||||||
|
|
||||||
Log.Verbose($"->{path}");
|
Log.Verbose($"->{path}");
|
||||||
|
|
||||||
HandleGetResourceHookAcquire(resourceHandle, path);
|
this.HandleGetResourceHookAcquire(resourceHandle, path);
|
||||||
|
|
||||||
return resourceHandle;
|
return resourceHandle;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Exception on ReadResourceSync hook.");
|
Log.Error(ex, "Exception on ReadResourceSync hook.");
|
||||||
|
|
||||||
return this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
|
return this.getResourceSyncHook.Original(manager, a2, a3, a4, pathPtr, a6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path) {
|
private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path)
|
||||||
|
{
|
||||||
if (FilePathHasInvalidChars(path))
|
if (FilePathHasInvalidChars(path))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.resourceHookMap.ContainsKey(handlePtr)) {
|
if (this.resourceHookMap.ContainsKey(handlePtr))
|
||||||
|
{
|
||||||
Log.Verbose($"-> Handle {handlePtr.ToInt64():X}({path}) was cached!");
|
Log.Verbose($"-> Handle {handlePtr.ToInt64():X}({path}) was cached!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hookInfo = new ResourceHandleHookInfo {
|
var hookInfo = new ResourceHandleHookInfo
|
||||||
Path = path
|
{
|
||||||
|
Path = path,
|
||||||
};
|
};
|
||||||
|
|
||||||
var hookPath = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "ResourceHook", path);
|
var hookPath = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "ResourceHook", path);
|
||||||
|
|
||||||
if (System.IO.File.Exists(hookPath)) {
|
if (System.IO.File.Exists(hookPath))
|
||||||
|
{
|
||||||
hookInfo.DetourFile = new FileStream(hookPath, FileMode.Open);
|
hookInfo.DetourFile = new FileStream(hookPath, FileMode.Open);
|
||||||
Log.Verbose("-> Added resource hook detour at {0}", hookPath);
|
Log.Verbose("-> Added resource hook detour at {0}", hookPath);
|
||||||
}
|
}
|
||||||
|
|
@ -137,10 +150,11 @@ namespace Dalamud.Game.Internal.File
|
||||||
this.resourceHookMap.Add(handlePtr, hookInfo);
|
this.resourceHookMap.Add(handlePtr, hookInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool FilePathHasInvalidChars(string path)
|
private class ResourceHandleHookInfo
|
||||||
{
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
return (!string.IsNullOrEmpty(path) && path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0);
|
public Stream DetourFile { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,28 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.File
|
namespace Dalamud.Game.Internal.File
|
||||||
{
|
{
|
||||||
class ResourceManagerAddressResolver : BaseAddressResolver
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="ResourceManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal class ResourceManagerAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the GetResourceAsync method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetResourceAsync { get; private set; }
|
public IntPtr GetResourceAsync { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the GetResourceSync method.
|
||||||
|
/// </summary>
|
||||||
public IntPtr GetResourceSync { get; private set; }
|
public IntPtr GetResourceSync { get; private set; }
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
/// <inheritdoc/>
|
||||||
GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74");
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6");
|
{
|
||||||
//ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
this.GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74");
|
||||||
|
this.GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6");
|
||||||
|
// ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Dalamud.Game.Network.Structures;
|
using Dalamud.Game.Network.Structures;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network {
|
namespace Dalamud.Game.Network
|
||||||
internal class MarketBoardItemRequest {
|
{
|
||||||
|
internal class MarketBoardItemRequest
|
||||||
|
{
|
||||||
public uint CatalogId { get; set; }
|
public uint CatalogId { get; set; }
|
||||||
|
|
||||||
public byte AmountToArrive { get; set; }
|
public byte AmountToArrive { get; set; }
|
||||||
|
|
||||||
public List<MarketBoardCurrentOfferings.MarketBoardItemListing> Listings { get; set; }
|
public List<MarketBoardCurrentOfferings.MarketBoardItemListing> Listings { get; set; }
|
||||||
|
|
||||||
public List<MarketBoardHistory.MarketBoardHistoryListing> History { get; set; }
|
public List<MarketBoardHistory.MarketBoardHistoryListing> History { get; set; }
|
||||||
|
|
||||||
public int ListingsRequestId { get; set; } = -1;
|
public int ListingsRequestId { get; set; } = -1;
|
||||||
|
|
||||||
public bool IsDone => Listings.Count == AmountToArrive && History.Count != 0;
|
public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,22 @@
|
||||||
using Dalamud.Game.Network.Structures;
|
using Dalamud.Game.Network.Structures;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders {
|
namespace Dalamud.Game.Network.MarketBoardUploaders
|
||||||
internal interface IMarketBoardUploader {
|
{
|
||||||
void Upload(MarketBoardItemRequest itemRequest);
|
/// <summary>
|
||||||
|
/// An interface binding for the Universalis uploader.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IMarketBoardUploader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Upload data about an item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item request data being uploaded.</param>
|
||||||
|
void Upload(MarketBoardItemRequest item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Upload tax rate data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taxRates">The tax rate data being uploaded.</param>
|
||||||
void UploadTax(MarketTaxRates taxRates);
|
void UploadTax(MarketTaxRates taxRates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,57 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||||
internal class UniversalisHistoryEntry {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Universalis API structure.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisHistoryEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the item is HQ or not.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("hq")]
|
[JsonProperty("hq")]
|
||||||
public bool Hq { get; set; }
|
public bool Hq { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item price per unit.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("pricePerUnit")]
|
[JsonProperty("pricePerUnit")]
|
||||||
public uint PricePerUnit { get; set; }
|
public uint PricePerUnit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the quantity of items available.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("quantity")]
|
[JsonProperty("quantity")]
|
||||||
public uint Quantity { get; set; }
|
public uint Quantity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the buyer.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("buyerName")]
|
[JsonProperty("buyerName")]
|
||||||
public string BuyerName { get; set; }
|
public string BuyerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this item was on a mannequin.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("onMannequin")]
|
[JsonProperty("onMannequin")]
|
||||||
public bool OnMannequin { get; set; }
|
public bool OnMannequin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the seller ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("sellerID")]
|
[JsonProperty("sellerID")]
|
||||||
public string SellerId { get; set; }
|
public string SellerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the buyer ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("buyerID")]
|
[JsonProperty("buyerID")]
|
||||||
public string BuyerId { get; set; }
|
public string BuyerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timestamp of the transaction.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("timestamp")]
|
[JsonProperty("timestamp")]
|
||||||
public long Timestamp { get; set; }
|
public long Timestamp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,35 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||||
internal class UniversalisHistoryUploadRequest {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Universalis API structure.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisHistoryUploadRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the world ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("worldID")]
|
[JsonProperty("worldID")]
|
||||||
public uint WorldId { get; set; }
|
public uint WorldId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("itemID")]
|
[JsonProperty("itemID")]
|
||||||
public uint ItemId { get; set; }
|
public uint ItemId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of available entries.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("entries")]
|
[JsonProperty("entries")]
|
||||||
public List<UniversalisHistoryEntry> Entries { get; set; }
|
public List<UniversalisHistoryEntry> Entries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the uploader ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("uploaderID")]
|
[JsonProperty("uploaderID")]
|
||||||
public string UploaderId { get; set; }
|
public string UploaderId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,95 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||||
internal class UniversalisItemListingsEntry {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Universalis API structure.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisItemListingsEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the listing ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("listingID")]
|
[JsonProperty("listingID")]
|
||||||
public string ListingId { get; set; }
|
public string ListingId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the item is HQ.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("hq")]
|
[JsonProperty("hq")]
|
||||||
public bool Hq { get; set; }
|
public bool Hq { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item price per unit.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("pricePerUnit")]
|
[JsonProperty("pricePerUnit")]
|
||||||
public uint PricePerUnit { get; set; }
|
public uint PricePerUnit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item quantity.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("quantity")]
|
[JsonProperty("quantity")]
|
||||||
public uint Quantity { get; set; }
|
public uint Quantity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the retainer selling the item.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("retainerName")]
|
[JsonProperty("retainerName")]
|
||||||
public string RetainerName { get; set; }
|
public string RetainerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ID of the retainer selling the item.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("retainerID")]
|
[JsonProperty("retainerID")]
|
||||||
public string RetainerId { get; set; }
|
public string RetainerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the user who created the entry.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("creatorName")]
|
[JsonProperty("creatorName")]
|
||||||
public string CreatorName { get; set; }
|
public string CreatorName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the item is on a mannequin.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("onMannequin")]
|
[JsonProperty("onMannequin")]
|
||||||
public bool OnMannequin { get; set; }
|
public bool OnMannequin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the seller ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("sellerID")]
|
[JsonProperty("sellerID")]
|
||||||
public string SellerId { get; set; }
|
public string SellerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ID of the user who created the entry.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("creatorID")]
|
[JsonProperty("creatorID")]
|
||||||
public string CreatorId { get; set; }
|
public string CreatorId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ID of the dye on the item.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("stainID")]
|
[JsonProperty("stainID")]
|
||||||
public int StainId { get; set; }
|
public int StainId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the city where the selling retainer resides.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("retainerCity")]
|
[JsonProperty("retainerCity")]
|
||||||
public int RetainerCity { get; set; }
|
public int RetainerCity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the last time the entry was reviewed.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("lastReviewTime")]
|
[JsonProperty("lastReviewTime")]
|
||||||
public long LastReviewTime { get; set; }
|
public long LastReviewTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the materia attached to the item.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("materia")]
|
[JsonProperty("materia")]
|
||||||
public List<UniversalisItemMateria> Materia { get; set; }
|
public List<UniversalisItemMateria> Materia { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,35 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||||
internal class UniversalisItemListingsUploadRequest {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Universalis API structure.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisItemListingsUploadRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the world ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("worldID")]
|
[JsonProperty("worldID")]
|
||||||
public uint WorldId { get; set; }
|
public uint WorldId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("itemID")]
|
[JsonProperty("itemID")]
|
||||||
public uint ItemId { get; set; }
|
public uint ItemId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of available items.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("listings")]
|
[JsonProperty("listings")]
|
||||||
public List<UniversalisItemListingsEntry> Listings { get; set; }
|
public List<UniversalisItemListingsEntry> Listings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the uploader ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("uploaderID")]
|
[JsonProperty("uploaderID")]
|
||||||
public string UploaderId { get; set; }
|
public string UploaderId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||||
internal class UniversalisItemMateria {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Universalis API structure.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisItemMateria
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item slot ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("slotID")]
|
[JsonProperty("slotID")]
|
||||||
public int SlotId { get; set; }
|
public int SlotId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the materia ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("materiaID")]
|
[JsonProperty("materiaID")]
|
||||||
public int MateriaId { get; set; }
|
public int MateriaId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,140 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
using Dalamud.Game.Network.MarketBoardUploaders;
|
||||||
using Dalamud.Game.Network.MarketBoardUploaders.Universalis;
|
using Dalamud.Game.Network.MarketBoardUploaders.Universalis;
|
||||||
using Dalamud.Game.Network.Structures;
|
using Dalamud.Game.Network.Structures;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders
|
||||||
internal class UniversalisMarketBoardUploader : IMarketBoardUploader {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an uploader for contributing data to Universalis.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
||||||
|
{
|
||||||
private const string ApiBase = "https://universalis.app";
|
private const string ApiBase = "https://universalis.app";
|
||||||
|
|
||||||
//private const string ApiBase = "https://127.0.0.1:443";
|
// private const string ApiBase = "https://127.0.0.1:443";
|
||||||
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
|
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
|
||||||
|
|
||||||
private readonly Dalamud dalamud;
|
private readonly Dalamud dalamud;
|
||||||
|
|
||||||
public UniversalisMarketBoardUploader(Dalamud dalamud) {
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UniversalisMarketBoardUploader"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public UniversalisMarketBoardUploader(Dalamud dalamud)
|
||||||
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Upload(MarketBoardItemRequest request) {
|
/// <inheritdoc/>
|
||||||
using (var client = new WebClient()) {
|
public void Upload(MarketBoardItemRequest request)
|
||||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
{
|
||||||
|
using var client = new WebClient();
|
||||||
|
|
||||||
Log.Verbose("Starting Universalis upload.");
|
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||||
var uploader = this.dalamud.ClientState.LocalContentId;
|
|
||||||
|
|
||||||
var listingsRequestObject = new UniversalisItemListingsUploadRequest();
|
Log.Verbose("Starting Universalis upload.");
|
||||||
listingsRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
var uploader = this.dalamud.ClientState.LocalContentId;
|
||||||
listingsRequestObject.UploaderId = uploader.ToString();
|
|
||||||
listingsRequestObject.ItemId = request.CatalogId;
|
|
||||||
|
|
||||||
listingsRequestObject.Listings = new List<UniversalisItemListingsEntry>();
|
var listingsRequestObject = new UniversalisItemListingsUploadRequest();
|
||||||
foreach (var marketBoardItemListing in request.Listings) {
|
listingsRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
||||||
var universalisListing = new UniversalisItemListingsEntry {
|
listingsRequestObject.UploaderId = uploader.ToString();
|
||||||
Hq = marketBoardItemListing.IsHq,
|
listingsRequestObject.ItemId = request.CatalogId;
|
||||||
SellerId = marketBoardItemListing.RetainerOwnerId.ToString(),
|
|
||||||
RetainerName = marketBoardItemListing.RetainerName,
|
|
||||||
RetainerId = marketBoardItemListing.RetainerId.ToString(),
|
|
||||||
CreatorId = marketBoardItemListing.ArtisanId.ToString(),
|
|
||||||
CreatorName = marketBoardItemListing.PlayerName,
|
|
||||||
OnMannequin = marketBoardItemListing.OnMannequin,
|
|
||||||
LastReviewTime = ((DateTimeOffset) marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
|
|
||||||
PricePerUnit = marketBoardItemListing.PricePerUnit,
|
|
||||||
Quantity = marketBoardItemListing.ItemQuantity,
|
|
||||||
RetainerCity = marketBoardItemListing.RetainerCityId
|
|
||||||
};
|
|
||||||
|
|
||||||
universalisListing.Materia = new List<UniversalisItemMateria>();
|
listingsRequestObject.Listings = new List<UniversalisItemListingsEntry>();
|
||||||
foreach (var itemMateria in marketBoardItemListing.Materia)
|
foreach (var marketBoardItemListing in request.Listings)
|
||||||
universalisListing.Materia.Add(new UniversalisItemMateria {
|
|
||||||
MateriaId = itemMateria.MateriaId,
|
|
||||||
SlotId = itemMateria.Index
|
|
||||||
});
|
|
||||||
|
|
||||||
listingsRequestObject.Listings.Add(universalisListing);
|
|
||||||
}
|
|
||||||
|
|
||||||
var upload = JsonConvert.SerializeObject(listingsRequestObject);
|
|
||||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload);
|
|
||||||
Log.Verbose(upload);
|
|
||||||
|
|
||||||
var historyRequestObject = new UniversalisHistoryUploadRequest();
|
|
||||||
historyRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
|
||||||
historyRequestObject.UploaderId = uploader.ToString();
|
|
||||||
historyRequestObject.ItemId = request.CatalogId;
|
|
||||||
|
|
||||||
historyRequestObject.Entries = new List<UniversalisHistoryEntry>();
|
|
||||||
foreach (var marketBoardHistoryListing in request.History)
|
|
||||||
historyRequestObject.Entries.Add(new UniversalisHistoryEntry {
|
|
||||||
BuyerName = marketBoardHistoryListing.BuyerName,
|
|
||||||
Hq = marketBoardHistoryListing.IsHq,
|
|
||||||
OnMannequin = marketBoardHistoryListing.OnMannequin,
|
|
||||||
PricePerUnit = marketBoardHistoryListing.SalePrice,
|
|
||||||
Quantity = marketBoardHistoryListing.Quantity,
|
|
||||||
Timestamp = ((DateTimeOffset) marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds()
|
|
||||||
});
|
|
||||||
|
|
||||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
|
||||||
|
|
||||||
var historyUpload = JsonConvert.SerializeObject(historyRequestObject);
|
|
||||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
|
|
||||||
Log.Verbose(historyUpload);
|
|
||||||
|
|
||||||
Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UploadTax(MarketTaxRates taxRates) {
|
|
||||||
using (var client = new WebClient())
|
|
||||||
{
|
{
|
||||||
var taxRatesRequest = new UniversalisTaxUploadRequest();
|
var universalisListing = new UniversalisItemListingsEntry
|
||||||
taxRatesRequest.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
{
|
||||||
taxRatesRequest.UploaderId = this.dalamud.ClientState.LocalContentId.ToString();
|
Hq = marketBoardItemListing.IsHq,
|
||||||
|
SellerId = marketBoardItemListing.RetainerOwnerId.ToString(),
|
||||||
taxRatesRequest.TaxData = new UniversalisTaxData {
|
RetainerName = marketBoardItemListing.RetainerName,
|
||||||
LimsaLominsa = taxRates.LimsaLominsaTax,
|
RetainerId = marketBoardItemListing.RetainerId.ToString(),
|
||||||
Gridania = taxRates.GridaniaTax,
|
CreatorId = marketBoardItemListing.ArtisanId.ToString(),
|
||||||
Uldah = taxRates.UldahTax,
|
CreatorName = marketBoardItemListing.PlayerName,
|
||||||
Ishgard = taxRates.IshgardTax,
|
OnMannequin = marketBoardItemListing.OnMannequin,
|
||||||
Kugane = taxRates.KuganeTax,
|
LastReviewTime = ((DateTimeOffset)marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
|
||||||
Crystarium = taxRates.CrystariumTax
|
PricePerUnit = marketBoardItemListing.PricePerUnit,
|
||||||
|
Quantity = marketBoardItemListing.ItemQuantity,
|
||||||
|
RetainerCity = marketBoardItemListing.RetainerCityId,
|
||||||
};
|
};
|
||||||
|
|
||||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
universalisListing.Materia = new List<UniversalisItemMateria>();
|
||||||
|
foreach (var itemMateria in marketBoardItemListing.Materia)
|
||||||
|
{
|
||||||
|
universalisListing.Materia.Add(new UniversalisItemMateria
|
||||||
|
{
|
||||||
|
MateriaId = itemMateria.MateriaId,
|
||||||
|
SlotId = itemMateria.Index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var historyUpload = JsonConvert.SerializeObject(taxRatesRequest);
|
listingsRequestObject.Listings.Add(universalisListing);
|
||||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
|
|
||||||
Log.Verbose(historyUpload);
|
|
||||||
|
|
||||||
Log.Verbose("Universalis tax upload completed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var upload = JsonConvert.SerializeObject(listingsRequestObject);
|
||||||
|
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload);
|
||||||
|
Log.Verbose(upload);
|
||||||
|
|
||||||
|
var historyRequestObject = new UniversalisHistoryUploadRequest();
|
||||||
|
historyRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
||||||
|
historyRequestObject.UploaderId = uploader.ToString();
|
||||||
|
historyRequestObject.ItemId = request.CatalogId;
|
||||||
|
|
||||||
|
historyRequestObject.Entries = new List<UniversalisHistoryEntry>();
|
||||||
|
foreach (var marketBoardHistoryListing in request.History)
|
||||||
|
{
|
||||||
|
historyRequestObject.Entries.Add(new UniversalisHistoryEntry
|
||||||
|
{
|
||||||
|
BuyerName = marketBoardHistoryListing.BuyerName,
|
||||||
|
Hq = marketBoardHistoryListing.IsHq,
|
||||||
|
OnMannequin = marketBoardHistoryListing.OnMannequin,
|
||||||
|
PricePerUnit = marketBoardHistoryListing.SalePrice,
|
||||||
|
Quantity = marketBoardHistoryListing.Quantity,
|
||||||
|
Timestamp = ((DateTimeOffset)marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||||
|
|
||||||
|
var historyUpload = JsonConvert.SerializeObject(historyRequestObject);
|
||||||
|
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
|
||||||
|
Log.Verbose(historyUpload);
|
||||||
|
|
||||||
|
Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UploadTax(MarketTaxRates taxRates)
|
||||||
|
{
|
||||||
|
using var client = new WebClient();
|
||||||
|
|
||||||
|
var taxRatesRequest = new UniversalisTaxUploadRequest();
|
||||||
|
taxRatesRequest.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
||||||
|
taxRatesRequest.UploaderId = this.dalamud.ClientState.LocalContentId.ToString();
|
||||||
|
|
||||||
|
taxRatesRequest.TaxData = new UniversalisTaxData
|
||||||
|
{
|
||||||
|
LimsaLominsa = taxRates.LimsaLominsaTax,
|
||||||
|
Gridania = taxRates.GridaniaTax,
|
||||||
|
Uldah = taxRates.UldahTax,
|
||||||
|
Ishgard = taxRates.IshgardTax,
|
||||||
|
Kugane = taxRates.KuganeTax,
|
||||||
|
Crystarium = taxRates.CrystariumTax,
|
||||||
|
};
|
||||||
|
|
||||||
|
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||||
|
|
||||||
|
var historyUpload = JsonConvert.SerializeObject(taxRatesRequest);
|
||||||
|
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
|
||||||
|
Log.Verbose(historyUpload);
|
||||||
|
|
||||||
|
Log.Verbose("Universalis tax upload completed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Universalis API structure.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisTaxData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Limsa Lominsa's current tax rate.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("limsaLominsa")]
|
||||||
|
public uint LimsaLominsa { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Gridania's current tax rate.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("gridania")]
|
||||||
|
public uint Gridania { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Ul'dah's current tax rate.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("uldah")]
|
||||||
|
public uint Uldah { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Ishgard's current tax rate.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("ishgard")]
|
||||||
|
public uint Ishgard { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Kugane's current tax rate.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("kugane")]
|
||||||
|
public uint Kugane { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets The Crystarium's current tax rate.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("crystarium")]
|
||||||
|
public uint Crystarium { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,41 +1,28 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||||
{
|
{
|
||||||
class UniversalisTaxUploadRequest
|
/// <summary>
|
||||||
|
/// A Universalis API structure.
|
||||||
|
/// </summary>
|
||||||
|
internal class UniversalisTaxUploadRequest
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the uploader's ID.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("uploaderID")]
|
[JsonProperty("uploaderID")]
|
||||||
public string UploaderId { get; set; }
|
public string UploaderId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the world to retrieve data from.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("worldID")]
|
[JsonProperty("worldID")]
|
||||||
public uint WorldId { get; set; }
|
public uint WorldId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets tax data for each city's market.
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("marketTaxRates")]
|
[JsonProperty("marketTaxRates")]
|
||||||
public UniversalisTaxData TaxData { get; set; }
|
public UniversalisTaxData TaxData { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class UniversalisTaxData {
|
|
||||||
[JsonProperty("limsaLominsa")]
|
|
||||||
public uint LimsaLominsa { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("gridania")]
|
|
||||||
public uint Gridania { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("uldah")]
|
|
||||||
public uint Uldah { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("ishgard")]
|
|
||||||
public uint Ishgard { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("kugane")]
|
|
||||||
public uint Kugane { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("crystarium")]
|
|
||||||
public uint Crystarium { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,47 +4,58 @@ using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Game.Internal.Network;
|
using Dalamud.Game.Internal.Network;
|
||||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
using Dalamud.Game.Network.MarketBoardUploaders;
|
||||||
using Dalamud.Game.Network.Structures;
|
using Dalamud.Game.Network.Structures;
|
||||||
using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
|
using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
|
||||||
using Lumina.Excel;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network {
|
namespace Dalamud.Game.Network
|
||||||
public class NetworkHandlers {
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class handles network notifications and uploading Marketboard data.
|
||||||
|
/// </summary>
|
||||||
|
public class NetworkHandlers
|
||||||
|
{
|
||||||
private readonly Dalamud dalamud;
|
private readonly Dalamud dalamud;
|
||||||
|
|
||||||
private readonly List<MarketBoardItemRequest> marketBoardRequests = new List<MarketBoardItemRequest>();
|
private readonly List<MarketBoardItemRequest> marketBoardRequests = new();
|
||||||
|
|
||||||
private readonly bool optOutMbUploads;
|
private readonly bool optOutMbUploads;
|
||||||
private readonly IMarketBoardUploader uploader;
|
private readonly IMarketBoardUploader uploader;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event which gets fired when a duty is ready.
|
/// Initializes a new instance of the <see cref="NetworkHandlers"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<ContentFinderCondition> CfPop;
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
/// <param name="optOutMbUploads">Whether the client should opt out of marketboard uploads.</param>
|
||||||
public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads) {
|
public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads)
|
||||||
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
this.optOutMbUploads = optOutMbUploads;
|
this.optOutMbUploads = optOutMbUploads;
|
||||||
|
|
||||||
this.uploader = new UniversalisMarketBoardUploader(dalamud);
|
this.uploader = new UniversalisMarketBoardUploader(dalamud);
|
||||||
|
|
||||||
dalamud.Framework.Network.OnNetworkMessage += OnNetworkMessage;
|
dalamud.Framework.Network.OnNetworkMessage += this.OnNetworkMessage;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) {
|
/// <summary>
|
||||||
|
/// Event which gets fired when a duty is ready.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ContentFinderCondition> CfPop;
|
||||||
|
|
||||||
|
private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction)
|
||||||
|
{
|
||||||
if (direction != NetworkMessageDirection.ZoneDown)
|
if (direction != NetworkMessageDirection.ZoneDown)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!this.dalamud.Data.IsDataReady)
|
if (!this.dalamud.Data.IsDataReady)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) {
|
if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"])
|
||||||
|
{
|
||||||
var data = new byte[64];
|
var data = new byte[64];
|
||||||
Marshal.Copy(dataPtr, data, 0, 64);
|
Marshal.Copy(dataPtr, data, 0, 64);
|
||||||
|
|
||||||
|
|
@ -63,98 +74,114 @@ namespace Dalamud.Game.Network {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfcName = contentFinderCondition.Name.ToString();
|
var cfcName = contentFinderCondition.Name.ToString();
|
||||||
if (string.IsNullOrEmpty(contentFinderCondition.Name)) {
|
if (string.IsNullOrEmpty(contentFinderCondition.Name))
|
||||||
|
{
|
||||||
cfcName = "Duty Roulette";
|
cfcName = "Duty Roulette";
|
||||||
contentFinderCondition.Image = 112324;
|
contentFinderCondition.Image = 112324;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.dalamud.Configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) {
|
if (this.dalamud.Configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated())
|
||||||
var flashInfo = new NativeFunctions.FLASHWINFO
|
{
|
||||||
|
var flashInfo = new NativeFunctions.FlashWindowInfo
|
||||||
{
|
{
|
||||||
cbSize = (uint)Marshal.SizeOf<NativeFunctions.FLASHWINFO>(),
|
cbSize = (uint)Marshal.SizeOf<NativeFunctions.FlashWindowInfo>(),
|
||||||
uCount = uint.MaxValue,
|
uCount = uint.MaxValue,
|
||||||
dwTimeout = 0,
|
dwTimeout = 0,
|
||||||
dwFlags = NativeFunctions.FlashWindow.FLASHW_ALL |
|
dwFlags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG,
|
||||||
NativeFunctions.FlashWindow.FLASHW_TIMERNOFG,
|
hwnd = Process.GetCurrentProcess().MainWindowHandle,
|
||||||
hwnd = Process.GetCurrentProcess().MainWindowHandle
|
|
||||||
};
|
};
|
||||||
NativeFunctions.FlashWindowEx(ref flashInfo);
|
NativeFunctions.FlashWindowEx(ref flashInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(() => {
|
Task.Run(() =>
|
||||||
if(this.dalamud.Configuration.DutyFinderChatMessage)
|
{
|
||||||
|
if (this.dalamud.Configuration.DutyFinderChatMessage)
|
||||||
this.dalamud.Framework.Gui.Chat.Print("Duty pop: " + cfcName);
|
this.dalamud.Framework.Gui.Chat.Print("Duty pop: " + cfcName);
|
||||||
|
|
||||||
CfPop?.Invoke(this, contentFinderCondition);
|
this.CfPop?.Invoke(this, contentFinderCondition);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.optOutMbUploads) {
|
if (!this.optOutMbUploads)
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"]) {
|
{
|
||||||
var catalogId = (uint) Marshal.ReadInt32(dataPtr);
|
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"])
|
||||||
|
{
|
||||||
|
var catalogId = (uint)Marshal.ReadInt32(dataPtr);
|
||||||
var amount = Marshal.ReadByte(dataPtr + 0xB);
|
var amount = Marshal.ReadByte(dataPtr + 0xB);
|
||||||
|
|
||||||
this.marketBoardRequests.Add(new MarketBoardItemRequest {
|
this.marketBoardRequests.Add(new MarketBoardItemRequest
|
||||||
|
{
|
||||||
CatalogId = catalogId,
|
CatalogId = catalogId,
|
||||||
AmountToArrive = amount,
|
AmountToArrive = amount,
|
||||||
Listings = new List<MarketBoardCurrentOfferings.MarketBoardItemListing>(),
|
Listings = new List<MarketBoardCurrentOfferings.MarketBoardItemListing>(),
|
||||||
History = new List<MarketBoardHistory.MarketBoardHistoryListing>()
|
History = new List<MarketBoardHistory.MarketBoardHistoryListing>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.Verbose($"NEW MB REQUEST START: item#{catalogId} amount#{amount}");
|
Log.Verbose($"NEW MB REQUEST START: item#{catalogId} amount#{amount}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"]) {
|
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"])
|
||||||
|
{
|
||||||
var listing = MarketBoardCurrentOfferings.Read(dataPtr);
|
var listing = MarketBoardCurrentOfferings.Read(dataPtr);
|
||||||
|
|
||||||
var request =
|
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
|
||||||
this.marketBoardRequests.LastOrDefault(
|
|
||||||
r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
|
|
||||||
|
|
||||||
if (request == null) {
|
if (request == null)
|
||||||
Log.Error(
|
{
|
||||||
$"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
|
Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) {
|
if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive)
|
||||||
Log.Error(
|
{
|
||||||
$"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
|
Log.Error($"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) {
|
if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId)
|
||||||
Log.Error(
|
{
|
||||||
$"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
|
Log.Error($"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.ListingsRequestId == -1 && request.Listings.Count > 0) {
|
if (request.ListingsRequestId == -1 && request.Listings.Count > 0)
|
||||||
Log.Error(
|
{
|
||||||
$"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
Log.Error($"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.ListingsRequestId == -1) {
|
if (request.ListingsRequestId == -1)
|
||||||
|
{
|
||||||
request.ListingsRequestId = listing.RequestId;
|
request.ListingsRequestId = listing.RequestId;
|
||||||
Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}");
|
Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Listings.AddRange(listing.ItemListings);
|
request.Listings.AddRange(listing.ItemListings);
|
||||||
|
|
||||||
Log.Verbose("Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
|
Log.Verbose(
|
||||||
listing.ItemListings.Count, request.ListingsRequestId, request.Listings.Count,
|
"Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
|
||||||
request.AmountToArrive, request.CatalogId);
|
listing.ItemListings.Count,
|
||||||
|
request.ListingsRequestId,
|
||||||
|
request.Listings.Count,
|
||||||
|
request.AmountToArrive,
|
||||||
|
request.CatalogId);
|
||||||
|
|
||||||
if (request.IsDone) {
|
if (request.IsDone)
|
||||||
Log.Verbose("Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
|
{
|
||||||
request.ListingsRequestId, request.CatalogId, request.AmountToArrive);
|
Log.Verbose(
|
||||||
try {
|
"Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
|
||||||
|
request.ListingsRequestId,
|
||||||
|
request.CatalogId,
|
||||||
|
request.AmountToArrive);
|
||||||
|
try
|
||||||
|
{
|
||||||
Task.Run(() => this.uploader.Upload(request));
|
Task.Run(() => this.uploader.Upload(request));
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Log.Error(ex, "Market Board data upload failed.");
|
Log.Error(ex, "Market Board data upload failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,18 +189,21 @@ namespace Dalamud.Game.Network {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"]) {
|
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"])
|
||||||
|
{
|
||||||
var listing = MarketBoardHistory.Read(dataPtr);
|
var listing = MarketBoardHistory.Read(dataPtr);
|
||||||
|
|
||||||
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId);
|
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId);
|
||||||
|
|
||||||
if (request == null) {
|
if (request == null)
|
||||||
|
{
|
||||||
Log.Error(
|
Log.Error(
|
||||||
$"Market Board data arrived without a corresponding request: item#{listing.CatalogId}");
|
$"Market Board data arrived without a corresponding request: item#{listing.CatalogId}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.ListingsRequestId != -1) {
|
if (request.ListingsRequestId != -1)
|
||||||
|
{
|
||||||
Log.Error(
|
Log.Error(
|
||||||
$"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
$"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
||||||
return;
|
return;
|
||||||
|
|
@ -183,7 +213,8 @@ namespace Dalamud.Game.Network {
|
||||||
|
|
||||||
Log.Verbose("Added history for item#{0}", listing.CatalogId);
|
Log.Verbose("Added history for item#{0}", listing.CatalogId);
|
||||||
|
|
||||||
if (request.AmountToArrive == 0) {
|
if (request.AmountToArrive == 0)
|
||||||
|
{
|
||||||
Log.Verbose("Request had 0 amount, uploading now");
|
Log.Verbose("Request had 0 amount, uploading now");
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -197,17 +228,25 @@ namespace Dalamud.Game.Network {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"]) {
|
if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"])
|
||||||
var category = (uint) Marshal.ReadInt32(dataPtr);
|
{
|
||||||
|
var category = (uint)Marshal.ReadInt32(dataPtr);
|
||||||
// Result dialog packet does not contain market tax rates
|
// Result dialog packet does not contain market tax rates
|
||||||
if (category != 720905) {
|
if (category != 720905)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var taxes = MarketTaxRates.Read(dataPtr);
|
var taxes = MarketTaxRates.Read(dataPtr);
|
||||||
|
|
||||||
Log.Verbose("MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
|
Log.Verbose(
|
||||||
taxes.LimsaLominsaTax, taxes.GridaniaTax, taxes.UldahTax, taxes.IshgardTax, taxes.KuganeTax, taxes.CrystariumTax);
|
"MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
|
||||||
|
taxes.LimsaLominsaTax,
|
||||||
|
taxes.GridaniaTax,
|
||||||
|
taxes.UldahTax,
|
||||||
|
taxes.IshgardTax,
|
||||||
|
taxes.KuganeTax,
|
||||||
|
taxes.CrystariumTax);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Task.Run(() => this.uploader.UploadTax(taxes));
|
Task.Run(() => this.uploader.UploadTax(taxes));
|
||||||
|
|
|
||||||
|
|
@ -17,69 +17,66 @@ namespace Dalamud.Game.Network.Structures
|
||||||
{
|
{
|
||||||
var output = new MarketBoardCurrentOfferings();
|
var output = new MarketBoardCurrentOfferings();
|
||||||
|
|
||||||
using (var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544))
|
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
|
||||||
|
using var reader = new BinaryReader(stream);
|
||||||
|
|
||||||
|
output.ItemListings = new List<MarketBoardItemListing>();
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
using (var reader = new BinaryReader(stream))
|
var listingEntry = new MarketBoardItemListing();
|
||||||
|
|
||||||
|
listingEntry.ListingId = reader.ReadUInt64();
|
||||||
|
listingEntry.RetainerId = reader.ReadUInt64();
|
||||||
|
listingEntry.RetainerOwnerId = reader.ReadUInt64();
|
||||||
|
listingEntry.ArtisanId = reader.ReadUInt64();
|
||||||
|
listingEntry.PricePerUnit = reader.ReadUInt32();
|
||||||
|
listingEntry.TotalTax = reader.ReadUInt32();
|
||||||
|
listingEntry.ItemQuantity = reader.ReadUInt32();
|
||||||
|
listingEntry.CatalogId = reader.ReadUInt32();
|
||||||
|
listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime;
|
||||||
|
|
||||||
|
reader.ReadUInt16(); // container
|
||||||
|
reader.ReadUInt32(); // slot
|
||||||
|
reader.ReadUInt16(); // durability
|
||||||
|
reader.ReadUInt16(); // spiritbond
|
||||||
|
|
||||||
|
listingEntry.Materia = new List<MarketBoardItemListing.ItemMateria>();
|
||||||
|
|
||||||
|
for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++)
|
||||||
{
|
{
|
||||||
output.ItemListings = new List<MarketBoardItemListing>();
|
var materiaVal = reader.ReadUInt16();
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
var materiaEntry = new MarketBoardItemListing.ItemMateria();
|
||||||
{
|
materiaEntry.MateriaId = (materiaVal & 0xFF0) >> 4;
|
||||||
var listingEntry = new MarketBoardItemListing();
|
materiaEntry.Index = materiaVal & 0xF;
|
||||||
|
|
||||||
listingEntry.ListingId = reader.ReadUInt64();
|
if (materiaEntry.MateriaId != 0)
|
||||||
listingEntry.RetainerId = reader.ReadUInt64();
|
listingEntry.Materia.Add(materiaEntry);
|
||||||
listingEntry.RetainerOwnerId = reader.ReadUInt64();
|
|
||||||
listingEntry.ArtisanId = reader.ReadUInt64();
|
|
||||||
listingEntry.PricePerUnit = reader.ReadUInt32();
|
|
||||||
listingEntry.TotalTax = reader.ReadUInt32();
|
|
||||||
listingEntry.ItemQuantity = reader.ReadUInt32();
|
|
||||||
listingEntry.CatalogId = reader.ReadUInt32();
|
|
||||||
listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime;
|
|
||||||
|
|
||||||
reader.ReadUInt16(); // container
|
|
||||||
reader.ReadUInt32(); // slot
|
|
||||||
reader.ReadUInt16(); // durability
|
|
||||||
reader.ReadUInt16(); // spiritbond
|
|
||||||
|
|
||||||
listingEntry.Materia = new List<MarketBoardItemListing.ItemMateria>();
|
|
||||||
|
|
||||||
for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++)
|
|
||||||
{
|
|
||||||
var materiaVal = reader.ReadUInt16();
|
|
||||||
|
|
||||||
var materiaEntry = new MarketBoardItemListing.ItemMateria();
|
|
||||||
materiaEntry.MateriaId = (materiaVal & 0xFF0) >> 4;
|
|
||||||
materiaEntry.Index = materiaVal & 0xF;
|
|
||||||
|
|
||||||
if (materiaEntry.MateriaId != 0)
|
|
||||||
listingEntry.Materia.Add(materiaEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.ReadUInt16();
|
|
||||||
reader.ReadUInt32();
|
|
||||||
|
|
||||||
listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
|
||||||
listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
|
||||||
listingEntry.IsHq = reader.ReadBoolean();
|
|
||||||
listingEntry.MateriaCount = reader.ReadByte();
|
|
||||||
listingEntry.OnMannequin = reader.ReadBoolean();
|
|
||||||
listingEntry.RetainerCityId = reader.ReadByte();
|
|
||||||
listingEntry.StainId = reader.ReadUInt16();
|
|
||||||
|
|
||||||
reader.ReadUInt16();
|
|
||||||
reader.ReadUInt32();
|
|
||||||
|
|
||||||
if (listingEntry.CatalogId != 0)
|
|
||||||
output.ItemListings.Add(listingEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.ListingIndexEnd = reader.ReadByte();
|
|
||||||
output.ListingIndexStart = reader.ReadByte();
|
|
||||||
output.RequestId = reader.ReadUInt16();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.ReadUInt16();
|
||||||
|
reader.ReadUInt32();
|
||||||
|
|
||||||
|
listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
||||||
|
listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
||||||
|
listingEntry.IsHq = reader.ReadBoolean();
|
||||||
|
listingEntry.MateriaCount = reader.ReadByte();
|
||||||
|
listingEntry.OnMannequin = reader.ReadBoolean();
|
||||||
|
listingEntry.RetainerCityId = reader.ReadByte();
|
||||||
|
listingEntry.StainId = reader.ReadUInt16();
|
||||||
|
|
||||||
|
reader.ReadUInt16();
|
||||||
|
reader.ReadUInt32();
|
||||||
|
|
||||||
|
if (listingEntry.CatalogId != 0)
|
||||||
|
output.ItemListings.Add(listingEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output.ListingIndexEnd = reader.ReadByte();
|
||||||
|
output.ListingIndexStart = reader.ReadByte();
|
||||||
|
output.RequestId = reader.ReadUInt16();
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,47 +3,52 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Structures {
|
namespace Dalamud.Game.Network.Structures
|
||||||
public class MarketBoardHistory {
|
{
|
||||||
|
public class MarketBoardHistory
|
||||||
|
{
|
||||||
public uint CatalogId;
|
public uint CatalogId;
|
||||||
public uint CatalogId2;
|
public uint CatalogId2;
|
||||||
|
|
||||||
public List<MarketBoardHistoryListing> HistoryListings;
|
public List<MarketBoardHistoryListing> HistoryListings;
|
||||||
|
|
||||||
public static unsafe MarketBoardHistory Read(IntPtr dataPtr) {
|
public static unsafe MarketBoardHistory Read(IntPtr dataPtr)
|
||||||
|
{
|
||||||
var output = new MarketBoardHistory();
|
var output = new MarketBoardHistory();
|
||||||
|
|
||||||
using (var stream = new UnmanagedMemoryStream((byte*) dataPtr.ToPointer(), 1544)) {
|
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
|
||||||
using (var reader = new BinaryReader(stream)) {
|
using var reader = new BinaryReader(stream);
|
||||||
output.CatalogId = reader.ReadUInt32();
|
|
||||||
output.CatalogId2 = reader.ReadUInt32();
|
|
||||||
|
|
||||||
output.HistoryListings = new List<MarketBoardHistoryListing>();
|
output.CatalogId = reader.ReadUInt32();
|
||||||
|
output.CatalogId2 = reader.ReadUInt32();
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++) {
|
output.HistoryListings = new List<MarketBoardHistoryListing>();
|
||||||
var listingEntry = new MarketBoardHistoryListing();
|
|
||||||
|
|
||||||
listingEntry.SalePrice = reader.ReadUInt32();
|
for (var i = 0; i < 10; i++)
|
||||||
listingEntry.PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime;
|
{
|
||||||
listingEntry.Quantity = reader.ReadUInt32();
|
var listingEntry = new MarketBoardHistoryListing
|
||||||
listingEntry.IsHq = reader.ReadBoolean();
|
{
|
||||||
|
SalePrice = reader.ReadUInt32(),
|
||||||
|
PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime,
|
||||||
|
Quantity = reader.ReadUInt32(),
|
||||||
|
IsHq = reader.ReadBoolean(),
|
||||||
|
};
|
||||||
|
|
||||||
reader.ReadBoolean();
|
reader.ReadBoolean();
|
||||||
|
|
||||||
listingEntry.OnMannequin = reader.ReadBoolean();
|
listingEntry.OnMannequin = reader.ReadBoolean();
|
||||||
listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000');
|
listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000');
|
||||||
listingEntry.CatalogId = reader.ReadUInt32();
|
listingEntry.CatalogId = reader.ReadUInt32();
|
||||||
|
|
||||||
if (listingEntry.CatalogId != 0)
|
if (listingEntry.CatalogId != 0)
|
||||||
output.HistoryListings.Add(listingEntry);
|
output.HistoryListings.Add(listingEntry);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MarketBoardHistoryListing {
|
public class MarketBoardHistoryListing
|
||||||
|
{
|
||||||
public string BuyerName;
|
public string BuyerName;
|
||||||
|
|
||||||
public uint CatalogId;
|
public uint CatalogId;
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Structures
|
|
||||||
{
|
|
||||||
public class MarketTaxRates
|
|
||||||
{
|
|
||||||
public uint LimsaLominsaTax;
|
|
||||||
public uint GridaniaTax;
|
|
||||||
public uint UldahTax;
|
|
||||||
public uint IshgardTax;
|
|
||||||
public uint KuganeTax;
|
|
||||||
public uint CrystariumTax;
|
|
||||||
|
|
||||||
|
|
||||||
public static unsafe MarketTaxRates Read(IntPtr dataPtr)
|
|
||||||
{
|
|
||||||
var output = new MarketTaxRates();
|
|
||||||
|
|
||||||
using (var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544))
|
|
||||||
{
|
|
||||||
using (var reader = new BinaryReader(stream))
|
|
||||||
{
|
|
||||||
stream.Position += 8;
|
|
||||||
|
|
||||||
output.LimsaLominsaTax = reader.ReadUInt32();
|
|
||||||
output.GridaniaTax = reader.ReadUInt32();
|
|
||||||
output.UldahTax = reader.ReadUInt32();
|
|
||||||
output.IshgardTax = reader.ReadUInt32();
|
|
||||||
output.KuganeTax = reader.ReadUInt32();
|
|
||||||
output.CrystariumTax = reader.ReadUInt32();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
Dalamud/Game/Network/Structures/MarketTaxRates.cs
Normal file
34
Dalamud/Game/Network/Structures/MarketTaxRates.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Network.Structures
|
||||||
|
{
|
||||||
|
public class MarketTaxRates
|
||||||
|
{
|
||||||
|
public uint LimsaLominsaTax;
|
||||||
|
public uint GridaniaTax;
|
||||||
|
public uint UldahTax;
|
||||||
|
public uint IshgardTax;
|
||||||
|
public uint KuganeTax;
|
||||||
|
public uint CrystariumTax;
|
||||||
|
|
||||||
|
public static unsafe MarketTaxRates Read(IntPtr dataPtr)
|
||||||
|
{
|
||||||
|
var output = new MarketTaxRates();
|
||||||
|
|
||||||
|
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
|
||||||
|
using var reader = new BinaryReader(stream);
|
||||||
|
|
||||||
|
stream.Position += 8;
|
||||||
|
|
||||||
|
output.LimsaLominsaTax = reader.ReadUInt32();
|
||||||
|
output.GridaniaTax = reader.ReadUInt32();
|
||||||
|
output.UldahTax = reader.ReadUInt32();
|
||||||
|
output.IshgardTax = reader.ReadUInt32();
|
||||||
|
output.KuganeTax = reader.ReadUInt32();
|
||||||
|
output.CrystariumTax = reader.ReadUInt32();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,28 +1,38 @@
|
||||||
using Dalamud.Hooking;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using Dalamud.Hooking;
|
||||||
|
|
||||||
namespace Dalamud.Game
|
namespace Dalamud.Game
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class enables TCP optimizations in the game socket for better performance.
|
||||||
|
/// </summary>
|
||||||
internal sealed class WinSockHandlers : IDisposable
|
internal sealed class WinSockHandlers : IDisposable
|
||||||
{
|
{
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
|
||||||
private delegate IntPtr SocketDelegate(int af, int type, int protocol);
|
|
||||||
private Hook<SocketDelegate> ws2SocketHook;
|
private Hook<SocketDelegate> ws2SocketHook;
|
||||||
|
|
||||||
[DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi)]
|
/// <summary>
|
||||||
private static extern int setsockopt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen);
|
/// Initializes a new instance of the <see cref="WinSockHandlers"/> class.
|
||||||
|
/// </summary>
|
||||||
public WinSockHandlers() {
|
public WinSockHandlers()
|
||||||
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", new SocketDelegate(OnSocket));
|
{
|
||||||
|
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", new SocketDelegate(this.OnSocket));
|
||||||
this.ws2SocketHook.Enable();
|
this.ws2SocketHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||||
|
private delegate IntPtr SocketDelegate(int af, int type, int protocol);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.ws2SocketHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private IntPtr OnSocket(int af, int type, int protocol)
|
private IntPtr OnSocket(int af, int type, int protocol)
|
||||||
{
|
{
|
||||||
var socket = this.ws2SocketHook.Original(af, type, protocol);
|
var socket = this.ws2SocketHook.Original(af, type, protocol);
|
||||||
|
|
@ -30,26 +40,22 @@ namespace Dalamud.Game
|
||||||
// IPPROTO_TCP
|
// IPPROTO_TCP
|
||||||
if (type == 1)
|
if (type == 1)
|
||||||
{
|
{
|
||||||
// INVALID_SOCKET
|
// INVALID_SOCKET
|
||||||
if (socket != new IntPtr(-1))
|
if (socket != new IntPtr(-1))
|
||||||
{
|
{
|
||||||
// In case you're not aware of it: (albeit you should)
|
// In case you're not aware of it: (albeit you should)
|
||||||
// https://linux.die.net/man/7/tcp
|
// https://linux.die.net/man/7/tcp
|
||||||
// https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf
|
// https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf
|
||||||
var value = new IntPtr(1);
|
var value = new IntPtr(1);
|
||||||
setsockopt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4);
|
NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4);
|
||||||
|
|
||||||
// Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards.
|
// Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards.
|
||||||
value = new IntPtr(1);
|
value = new IntPtr(1);
|
||||||
setsockopt(socket, SocketOptionLevel.Tcp, (SocketOptionName)12, ref value, 4);
|
NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.AddMembership, ref value, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
ws2SocketHook.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Dalamud.Game
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base address of the .text section search area.
|
/// Gets the base address of the .text section search area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr TextSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.TextSectionOffset);
|
public IntPtr TextSectionBase => new(this.SearchBase.ToInt64() + this.TextSectionOffset);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the offset of the .text section from the base of the module.
|
/// Gets the offset of the .text section from the base of the module.
|
||||||
|
|
@ -71,7 +71,7 @@ namespace Dalamud.Game
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base address of the .data section search area.
|
/// Gets the base address of the .data section search area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr DataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.DataSectionOffset);
|
public IntPtr DataSectionBase => new(this.SearchBase.ToInt64() + this.DataSectionOffset);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the offset of the .data section from the base of the module.
|
/// Gets the offset of the .data section from the base of the module.
|
||||||
|
|
@ -86,7 +86,7 @@ namespace Dalamud.Game
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base address of the .rdata section search area.
|
/// Gets the base address of the .rdata section search area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr RDataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.RDataSectionOffset);
|
public IntPtr RDataSectionBase => new(this.SearchBase.ToInt64() + this.RDataSectionOffset);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the offset of the .rdata section from the base of the module.
|
/// Gets the offset of the .rdata section from the base of the module.
|
||||||
|
|
@ -230,7 +230,7 @@ namespace Dalamud.Game
|
||||||
return IntPtr.Add(sigLocation, 5 + jumpOffset);
|
return IntPtr.Add(sigLocation, 5 + jumpOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (byte[] needle, bool[] mask) ParseSignature(string signature)
|
private static (byte[] Needle, bool[] Mask) ParseSignature(string signature)
|
||||||
{
|
{
|
||||||
signature = signature.Replace(" ", string.Empty);
|
signature = signature.Replace(" ", string.Empty);
|
||||||
if (signature.Length % 2 != 0)
|
if (signature.Length % 2 != 0)
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ namespace Dalamud.Game.Text.Sanitizer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Sanitizer : ISanitizer
|
public class Sanitizer : ISanitizer
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<string, string> DESanitizationDict = new Dictionary<string, string>
|
private static readonly Dictionary<string, string> DESanitizationDict = new()
|
||||||
{
|
{
|
||||||
{ "\u0020\u2020", string.Empty }, // dagger
|
{ "\u0020\u2020", string.Empty }, // dagger
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> FRSanitizationDict = new Dictionary<string, string>
|
private static readonly Dictionary<string, string> FRSanitizationDict = new()
|
||||||
{
|
{
|
||||||
{ "\u0153", "\u006F\u0065" }, // ligature oe
|
{ "\u0153", "\u006F\u0065" }, // ligature oe
|
||||||
};
|
};
|
||||||
|
|
@ -75,18 +75,13 @@ namespace Dalamud.Game.Text.Sanitizer
|
||||||
private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage)
|
private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage)
|
||||||
{
|
{
|
||||||
var sanitizedString = FilterUnprintableCharacters(unsanitizedString);
|
var sanitizedString = FilterUnprintableCharacters(unsanitizedString);
|
||||||
switch (clientLanguage)
|
return clientLanguage switch
|
||||||
{
|
{
|
||||||
case ClientLanguage.Japanese:
|
ClientLanguage.Japanese or ClientLanguage.English => sanitizedString,
|
||||||
case ClientLanguage.English:
|
ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict),
|
||||||
return sanitizedString;
|
ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict),
|
||||||
case ClientLanguage.German:
|
_ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null),
|
||||||
return FilterByDict(sanitizedString, DESanitizationDict);
|
};
|
||||||
case ClientLanguage.French:
|
|
||||||
return FilterByDict(sanitizedString, FRSanitizationDict);
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> SanitizeByLanguage(
|
private static IEnumerable<string> SanitizeByLanguage(
|
||||||
|
|
|
||||||
|
|
@ -1,182 +1,743 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
#pragma warning disable 1591
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Text
|
namespace Dalamud.Game.Text
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Special unicode characters with game-related symbols that work both in-game and in any dalamud window.
|
/// Special unicode characters with game-related symbols that work both in-game and in any dalamud window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum SeIconChar {
|
public enum SeIconChar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The sprout icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BotanistSprout = 0xE034,
|
BotanistSprout = 0xE034,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The item level icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ItemLevel = 0xE033,
|
ItemLevel = 0xE033,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The auto translate open icon unicode character.
|
||||||
|
/// </summary>
|
||||||
AutoTranslateOpen = 0xE040,
|
AutoTranslateOpen = 0xE040,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The auto translate close icon unicode character.
|
||||||
|
/// </summary>
|
||||||
AutoTranslateClose = 0xE041,
|
AutoTranslateClose = 0xE041,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The high quality icon unicode character.
|
||||||
|
/// </summary>
|
||||||
HighQuality = 0xE03C,
|
HighQuality = 0xE03C,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The clock icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Clock = 0xE031,
|
Clock = 0xE031,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gil icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Gil = 0xE049,
|
Gil = 0xE049,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Hydaelyn icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Hyadelyn = 0xE048,
|
Hyadelyn = 0xE048,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The no mouse click icon unicode character.
|
||||||
|
/// </summary>
|
||||||
MouseNoClick = 0xE050,
|
MouseNoClick = 0xE050,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The left mouse click icon unicode character.
|
||||||
|
/// </summary>
|
||||||
MouseLeftClick = 0xE051,
|
MouseLeftClick = 0xE051,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The right mouse click icon unicode character.
|
||||||
|
/// </summary>
|
||||||
MouseRightClick = 0xE052,
|
MouseRightClick = 0xE052,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The left/right mouse click icon unicode character.
|
||||||
|
/// </summary>
|
||||||
MouseBothClick = 0xE053,
|
MouseBothClick = 0xE053,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse wheel icon unicode character.
|
||||||
|
/// </summary>
|
||||||
MouseWheel = 0xE054,
|
MouseWheel = 0xE054,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse with a 1 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Mouse1 = 0xE055,
|
Mouse1 = 0xE055,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse with a 2 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Mouse2 = 0xE056,
|
Mouse2 = 0xE056,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse with a 3 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Mouse3 = 0xE057,
|
Mouse3 = 0xE057,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse with a 4 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Mouse4 = 0xE058,
|
Mouse4 = 0xE058,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse with a 5 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Mouse5 = 0xE059,
|
Mouse5 = 0xE059,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The level English icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LevelEn = 0xE06A,
|
LevelEn = 0xE06A,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The level German icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LevelDe = 0xE06B,
|
LevelDe = 0xE06B,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The level French icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LevelFr = 0xE06C,
|
LevelFr = 0xE06C,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The experience icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Experience = 0xE0BC,
|
Experience = 0xE0BC,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The experience filled icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ExperienceFilled = 0xE0BD,
|
ExperienceFilled = 0xE0BD,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The A.M. time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
TimeAm = 0xE06D,
|
TimeAm = 0xE06D,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The P.M. time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
TimePm = 0xE06E,
|
TimePm = 0xE06E,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The right arrow icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ArrowRight = 0xE06F,
|
ArrowRight = 0xE06F,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The down arrow icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ArrowDown = 0xE035,
|
ArrowDown = 0xE035,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 0 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number0 = 0xE060,
|
Number0 = 0xE060,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 1 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number1 = 0xE061,
|
Number1 = 0xE061,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 2 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number2 = 0xE062,
|
Number2 = 0xE062,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 3 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number3 = 0xE063,
|
Number3 = 0xE063,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 4 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number4 = 0xE064,
|
Number4 = 0xE064,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 5 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number5 = 0xE065,
|
Number5 = 0xE065,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 6 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number6 = 0xE066,
|
Number6 = 0xE066,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 7 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number7 = 0xE067,
|
Number7 = 0xE067,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 8 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number8 = 0xE068,
|
Number8 = 0xE068,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number 9 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Number9 = 0xE069,
|
Number9 = 0xE069,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 0 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber0 = 0xE08F,
|
BoxedNumber0 = 0xE08F,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 1 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber1 = 0xE090,
|
BoxedNumber1 = 0xE090,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 2 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber2 = 0xE091,
|
BoxedNumber2 = 0xE091,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 3 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber3 = 0xE092,
|
BoxedNumber3 = 0xE092,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 4 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber4 = 0xE093,
|
BoxedNumber4 = 0xE093,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 5 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber5 = 0xE094,
|
BoxedNumber5 = 0xE094,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 6 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber6 = 0xE095,
|
BoxedNumber6 = 0xE095,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 7 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber7 = 0xE096,
|
BoxedNumber7 = 0xE096,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 8 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber8 = 0xE097,
|
BoxedNumber8 = 0xE097,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 9 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber9 = 0xE098,
|
BoxedNumber9 = 0xE098,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 10 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber10 = 0xE099,
|
BoxedNumber10 = 0xE099,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 11 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber11 = 0xE09A,
|
BoxedNumber11 = 0xE09A,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 12 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber12 = 0xE09B,
|
BoxedNumber12 = 0xE09B,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 13 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber13 = 0xE09C,
|
BoxedNumber13 = 0xE09C,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 14 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber14 = 0xE09D,
|
BoxedNumber14 = 0xE09D,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 15 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber15 = 0xE09E,
|
BoxedNumber15 = 0xE09E,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 16 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber16 = 0xE09F,
|
BoxedNumber16 = 0xE09F,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 17 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber17 = 0xE0A0,
|
BoxedNumber17 = 0xE0A0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 18 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber18 = 0xE0A1,
|
BoxedNumber18 = 0xE0A1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 19 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber19 = 0xE0A2,
|
BoxedNumber19 = 0xE0A2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 20 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber20 = 0xE0A3,
|
BoxedNumber20 = 0xE0A3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 21 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber21 = 0xE0A4,
|
BoxedNumber21 = 0xE0A4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 22 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber22 = 0xE0A5,
|
BoxedNumber22 = 0xE0A5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 23 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber23 = 0xE0A6,
|
BoxedNumber23 = 0xE0A6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 24 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber24 = 0xE0A7,
|
BoxedNumber24 = 0xE0A7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 25 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber25 = 0xE0A8,
|
BoxedNumber25 = 0xE0A8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 26 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber26 = 0xE0A9,
|
BoxedNumber26 = 0xE0A9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 27 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber27 = 0xE0AA,
|
BoxedNumber27 = 0xE0AA,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 28 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber28 = 0xE0AB,
|
BoxedNumber28 = 0xE0AB,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 29 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber29 = 0xE0AC,
|
BoxedNumber29 = 0xE0AC,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 30 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber30 = 0xE0AD,
|
BoxedNumber30 = 0xE0AD,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed number 31 icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedNumber31 = 0xE0AE,
|
BoxedNumber31 = 0xE0AE,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed plus icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedPlus = 0xE0AF,
|
BoxedPlus = 0xE0AF,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bosed question mark icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedQuestionMark = 0xE070,
|
BoxedQuestionMark = 0xE070,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed star icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedStar = 0xE0C0,
|
BoxedStar = 0xE0C0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed Roman numeral 1 (I) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedRoman1 = 0xE0C1,
|
BoxedRoman1 = 0xE0C1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed Roman numeral 2 (II) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedRoman2 = 0xE0C2,
|
BoxedRoman2 = 0xE0C2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed Roman numeral 3 (III) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedRoman3 = 0xE0C3,
|
BoxedRoman3 = 0xE0C3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed Roman numeral 4 (IV) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedRoman4 = 0xE0C4,
|
BoxedRoman4 = 0xE0C4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed Roman numeral 5 (V) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedRoman5 = 0xE0C5,
|
BoxedRoman5 = 0xE0C5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed Roman numeral 6 (VI) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedRoman6 = 0xE0C6,
|
BoxedRoman6 = 0xE0C6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter A icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterA = 0xE071,
|
BoxedLetterA = 0xE071,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter B icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterB = 0xE072,
|
BoxedLetterB = 0xE072,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter C icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterC = 0xE073,
|
BoxedLetterC = 0xE073,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter D icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterD = 0xE074,
|
BoxedLetterD = 0xE074,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter E icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterE = 0xE075,
|
BoxedLetterE = 0xE075,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter F icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterF = 0xE076,
|
BoxedLetterF = 0xE076,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter G icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterG = 0xE077,
|
BoxedLetterG = 0xE077,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter H icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterH = 0xE078,
|
BoxedLetterH = 0xE078,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter I icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterI = 0xE079,
|
BoxedLetterI = 0xE079,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter J icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterJ = 0xE07A,
|
BoxedLetterJ = 0xE07A,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter K icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterK = 0xE07B,
|
BoxedLetterK = 0xE07B,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter L icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterL = 0xE07C,
|
BoxedLetterL = 0xE07C,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter M icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterM = 0xE07D,
|
BoxedLetterM = 0xE07D,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter N icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterN = 0xE07E,
|
BoxedLetterN = 0xE07E,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter O icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterO = 0xE07F,
|
BoxedLetterO = 0xE07F,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter P icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterP = 0xE080,
|
BoxedLetterP = 0xE080,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter Q icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterQ = 0xE081,
|
BoxedLetterQ = 0xE081,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter R icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterR = 0xE082,
|
BoxedLetterR = 0xE082,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter S icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterS = 0xE083,
|
BoxedLetterS = 0xE083,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter T icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterT = 0xE084,
|
BoxedLetterT = 0xE084,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter U icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterU = 0xE085,
|
BoxedLetterU = 0xE085,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter V icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterV = 0xE086,
|
BoxedLetterV = 0xE086,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter W icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterW = 0xE087,
|
BoxedLetterW = 0xE087,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter X icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterX = 0xE088,
|
BoxedLetterX = 0xE088,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter Y icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterY = 0xE089,
|
BoxedLetterY = 0xE089,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boxed letter Z icon unicode character.
|
||||||
|
/// </summary>
|
||||||
BoxedLetterZ = 0xE08A,
|
BoxedLetterZ = 0xE08A,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The circle icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Circle = 0xE04A,
|
Circle = 0xE04A,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The square icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Square = 0xE04B,
|
Square = 0xE04B,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cross icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Cross = 0xE04C,
|
Cross = 0xE04C,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The triangle icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Triangle = 0xE04D,
|
Triangle = 0xE04D,
|
||||||
Hexagon = 0xE042,
|
|
||||||
|
/// <summary>
|
||||||
|
/// The hexagon icon unicode character.
|
||||||
|
/// </summary>
|
||||||
|
Hexagon = 0xE042,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The no-circle/prohobited icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Prohibited = 0xE043,
|
Prohibited = 0xE043,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The dice icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Dice = 0xE03E,
|
Dice = 0xE03E,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The debuff icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Debuff = 0xE05B,
|
Debuff = 0xE05B,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buff icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Buff = 0xE05C,
|
Buff = 0xE05C,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cross-world icon unicode character.
|
||||||
|
/// </summary>
|
||||||
CrossWorld = 0xE05D,
|
CrossWorld = 0xE05D,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Eureka level icon unicode character.
|
||||||
|
/// </summary>
|
||||||
EurekaLevel = 0xE03A,
|
EurekaLevel = 0xE03A,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The link marker icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LinkMarker = 0xE0BB,
|
LinkMarker = 0xE0BB,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The glamoured icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Glamoured = 0xE03B,
|
Glamoured = 0xE03B,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The glamoured and dyed icon unicode character.
|
||||||
|
/// </summary>
|
||||||
GlamouredDyed = 0xE04E,
|
GlamouredDyed = 0xE04E,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The synced quest icon unicode character.
|
||||||
|
/// </summary>
|
||||||
QuestSync = 0xE0BE,
|
QuestSync = 0xE0BE,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The repeatable quest icon unicode character.
|
||||||
|
/// </summary>
|
||||||
QuestRepeatable = 0xE0BF,
|
QuestRepeatable = 0xE0BF,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IME hiragana icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ImeHiragana = 0xE020,
|
ImeHiragana = 0xE020,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IME katakana icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ImeKatakana = 0xE021,
|
ImeKatakana = 0xE021,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IME alphanumeric icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ImeAlphanumeric = 0xE022,
|
ImeAlphanumeric = 0xE022,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IME katakana half-width icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ImeKatakanaHalfWidth = 0xE023,
|
ImeKatakanaHalfWidth = 0xE023,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IME alphanumeric half-width icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ImeAlphanumericHalfWidth = 0xE024,
|
ImeAlphanumericHalfWidth = 0xE024,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (1) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance1 = 0xE0B1,
|
Instance1 = 0xE0B1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (2) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance2 = 0xE0B2,
|
Instance2 = 0xE0B2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (3) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance3 = 0xE0B3,
|
Instance3 = 0xE0B3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (4) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance4 = 0xE0B4,
|
Instance4 = 0xE0B4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (5) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance5 = 0xE0B5,
|
Instance5 = 0xE0B5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (6) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance6 = 0xE0B6,
|
Instance6 = 0xE0B6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (7) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance7 = 0xE0B7,
|
Instance7 = 0xE0B7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (8) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance8 = 0xE0B8,
|
Instance8 = 0xE0B8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance (9) icon unicode character.
|
||||||
|
/// </summary>
|
||||||
Instance9 = 0xE0B9,
|
Instance9 = 0xE0B9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instance merged icon unicode character.
|
||||||
|
/// </summary>
|
||||||
InstanceMerged = 0xE0BA,
|
InstanceMerged = 0xE0BA,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The English local time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LocalTimeEn = 0xE0D0,
|
LocalTimeEn = 0xE0D0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The English server time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ServerTimeEn = 0xE0D1,
|
ServerTimeEn = 0xE0D1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The English Eorzea time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
EorzeaTimeEn = 0xE0D2,
|
EorzeaTimeEn = 0xE0D2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The German local time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LocalTimeDe = 0xE0D3,
|
LocalTimeDe = 0xE0D3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The German server time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ServerTimeDe = 0xE0D4,
|
ServerTimeDe = 0xE0D4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The German Eorzea time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
EorzeaTimeDe = 0xE0D5,
|
EorzeaTimeDe = 0xE0D5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The French local time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LocalTimeFr = 0xE0D6,
|
LocalTimeFr = 0xE0D6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The French server time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ServerTimeFr = 0xE0D7,
|
ServerTimeFr = 0xE0D7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The French Eorzea time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
EorzeaTimeFr = 0xE0D8,
|
EorzeaTimeFr = 0xE0D8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Japanese local time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
LocalTimeJa = 0xE0D9,
|
LocalTimeJa = 0xE0D9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Japanese server time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
ServerTimeJa = 0xE0DA,
|
ServerTimeJa = 0xE0DA,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Japanese Eorzea time icon unicode character.
|
||||||
|
/// </summary>
|
||||||
EorzeaTimeJa = 0xE0DB,
|
EorzeaTimeJa = 0xE0DB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,120 +1,438 @@
|
||||||
#pragma warning disable 1591
|
namespace Dalamud.Game.Text.SeStringHandling
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents special icons that can appear in chat naturally or as IconPayloads.
|
||||||
|
/// </summary>
|
||||||
|
public enum BitmapFontIcon : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No icon.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling {
|
/// <summary>
|
||||||
public enum BitmapFontIcon : uint {
|
/// The controller D-pad up icon.
|
||||||
None,
|
/// </summary>
|
||||||
ControllerDPadUp,
|
ControllerDPadUp = 1,
|
||||||
ControllerDPadDown,
|
|
||||||
ControllerDPadLeft,
|
|
||||||
ControllerDPadRight,
|
|
||||||
ControllerDPadUpDown,
|
|
||||||
ControllerDPadLeftRight,
|
|
||||||
ControllerDPadAll,
|
|
||||||
|
|
||||||
ControllerButton0, // Xbox B / PS Circle
|
/// <summary>
|
||||||
ControllerButton1, // Xbox A / PS Cross
|
/// The controller D-pad down icon.
|
||||||
ControllerButton2, // Xbox X / PS Square
|
/// </summary>
|
||||||
ControllerButton3, // Xbox Y / PS Triangle
|
ControllerDPadDown = 2,
|
||||||
|
|
||||||
ControllerShoulderLeft,
|
/// <summary>
|
||||||
ControllerShoulderRight,
|
/// The controller D-pad left icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerDPadLeft = 3,
|
||||||
|
|
||||||
ControllerTriggerLeft,
|
/// <summary>
|
||||||
ControllerTriggerRight,
|
/// The controller D-pad right icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerDPadRight = 4,
|
||||||
|
|
||||||
ControllerAnalogLeftStickIn,
|
/// <summary>
|
||||||
ControllerAnalogRightStickIn,
|
/// The controller D-pad up/down icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerDPadUpDown = 5,
|
||||||
|
|
||||||
ControllerStart,
|
/// <summary>
|
||||||
ControllerBack,
|
/// The controller D-pad left/right icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerDPadLeftRight = 6,
|
||||||
|
|
||||||
ControllerAnalogLeftStick,
|
/// <summary>
|
||||||
ControllerAnalogLeftStickUpDown,
|
/// The controller D-pad all directions icon.
|
||||||
ControllerAnalogLeftStickLeftRight,
|
/// </summary>
|
||||||
|
ControllerDPadAll = 7,
|
||||||
|
|
||||||
ControllerAnalogRightStick,
|
/// <summary>
|
||||||
ControllerAnalogRightStickUpDown,
|
/// The controller button 0 icon (Xbox: B, PlayStation: Circle).
|
||||||
ControllerAnalogRightStickLeftRight,
|
/// </summary>
|
||||||
|
ControllerButton0 = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller button 1 icon (XBox: A, PlayStation: Cross).
|
||||||
|
/// </summary>
|
||||||
|
ControllerButton1 = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller button 2 icon (XBox: X, PlayStation: Square).
|
||||||
|
/// </summary>
|
||||||
|
ControllerButton2 = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller button 3 icon (BBox: Y, PlayStation: Triangle).
|
||||||
|
/// </summary>
|
||||||
|
ControllerButton3 = 11,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller left shoulder button icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerShoulderLeft = 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller right shoulder button icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerShoulderRight = 13,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller left trigger button icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerTriggerLeft = 14,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller right trigger button icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerTriggerRight = 15,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller left analog stick in icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogLeftStickIn = 16,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller right analog stick in icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogRightStickIn = 17,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller start button icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerStart = 18,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller back button icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerBack = 19,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller left analog stick icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogLeftStick = 20,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller left analog stick up/down icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogLeftStickUpDown = 21,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller left analog stick left/right icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogLeftStickLeftRight = 22,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller right analog stick icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogRightStick = 23,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller right analog stick up/down icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogRightStickUpDown = 24,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The controller right analog stick left/right icon.
|
||||||
|
/// </summary>
|
||||||
|
ControllerAnalogRightStickLeftRight = 25,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The La Noscea region icon.
|
||||||
|
/// </summary>
|
||||||
LaNoscea = 51,
|
LaNoscea = 51,
|
||||||
BlackShroud,
|
|
||||||
Thanalan,
|
|
||||||
AutoTranslateBegin,
|
|
||||||
AutoTranslateEnd,
|
|
||||||
ElementFire,
|
|
||||||
ElementIce,
|
|
||||||
ElementWind,
|
|
||||||
ElementEarth,
|
|
||||||
ElementLightning,
|
|
||||||
ElementWater,
|
|
||||||
LevelSync,
|
|
||||||
Warning,
|
|
||||||
Ishgard,
|
|
||||||
Aetheryte,
|
|
||||||
Aethernet,
|
|
||||||
|
|
||||||
GoldStar,
|
/// <summary>
|
||||||
SilverStar,
|
/// The Black Shroud region icon.
|
||||||
|
/// </summary>
|
||||||
|
BlackShroud = 52,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Thanalan region icon.
|
||||||
|
/// </summary>
|
||||||
|
Thanalan = 53,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The auto translate begin icon.
|
||||||
|
/// </summary>
|
||||||
|
AutoTranslateBegin = 54,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The auto translate end icon.
|
||||||
|
/// </summary>
|
||||||
|
AutoTranslateEnd = 55,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fire element icon.
|
||||||
|
/// </summary>
|
||||||
|
ElementFire = 56,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ice element icon.
|
||||||
|
/// </summary>
|
||||||
|
ElementIce = 57,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The wind element icon.
|
||||||
|
/// </summary>
|
||||||
|
ElementWind = 58,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The earth element icon.
|
||||||
|
/// </summary>
|
||||||
|
ElementEarth = 59,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The lightning element icon.
|
||||||
|
/// </summary>
|
||||||
|
ElementLightning = 60,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The water element icon.
|
||||||
|
/// </summary>
|
||||||
|
ElementWater = 61,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The level sync icon.
|
||||||
|
/// </summary>
|
||||||
|
LevelSync = 62,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The warning icon.
|
||||||
|
/// </summary>
|
||||||
|
Warning = 63,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Ishgard region icon.
|
||||||
|
/// </summary>
|
||||||
|
Ishgard = 64,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Aetheryte icon.
|
||||||
|
/// </summary>
|
||||||
|
Aetheryte = 65,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Aethernet icon.
|
||||||
|
/// </summary>
|
||||||
|
Aethernet = 66,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gold star icon.
|
||||||
|
/// </summary>
|
||||||
|
GoldStar = 67,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The silver star icon.
|
||||||
|
/// </summary>
|
||||||
|
SilverStar = 68,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The green dot icon.
|
||||||
|
/// </summary>
|
||||||
GreenDot = 70,
|
GreenDot = 70,
|
||||||
SwordUnsheathed,
|
|
||||||
SwordSheathed,
|
|
||||||
|
|
||||||
Dice,
|
/// <summary>
|
||||||
|
/// The unsheathed sword icon.
|
||||||
|
/// </summary>
|
||||||
|
SwordUnsheathed = 71,
|
||||||
|
|
||||||
FlyZone,
|
/// <summary>
|
||||||
FlyZoneLocked,
|
/// The sheathed sword icon.
|
||||||
|
/// </summary>
|
||||||
|
SwordSheathed = 72,
|
||||||
|
|
||||||
NoCircle,
|
/// <summary>
|
||||||
|
/// The dice icon.
|
||||||
|
/// </summary>
|
||||||
|
Dice = 73,
|
||||||
|
|
||||||
NewAdventurer,
|
/// <summary>
|
||||||
Mentor,
|
/// The flyable zone icon.
|
||||||
MentorPvE,
|
/// </summary>
|
||||||
MentorCrafting,
|
FlyZone = 74,
|
||||||
MentorPvP,
|
|
||||||
|
|
||||||
Tank,
|
/// <summary>
|
||||||
Healer,
|
/// The no-flying zone icon.
|
||||||
DPS,
|
/// </summary>
|
||||||
Crafter,
|
FlyZoneLocked = 75,
|
||||||
Gatherer,
|
|
||||||
AnyClass,
|
|
||||||
|
|
||||||
CrossWorld,
|
/// <summary>
|
||||||
|
/// The no-circle/prohibited icon.
|
||||||
|
/// </summary>
|
||||||
|
NoCircle = 76,
|
||||||
|
|
||||||
FateSlay,
|
/// <summary>
|
||||||
FateBoss,
|
/// The sprout icon.
|
||||||
FateGather,
|
/// </summary>
|
||||||
FateDefend,
|
NewAdventurer = 77,
|
||||||
FateEscort,
|
|
||||||
FateSpecial1,
|
|
||||||
|
|
||||||
Returner,
|
/// <summary>
|
||||||
|
/// The mentor icon.
|
||||||
|
/// </summary>
|
||||||
|
Mentor = 78,
|
||||||
|
|
||||||
FarEast,
|
/// <summary>
|
||||||
GyrAbania,
|
/// The PvE mentor icon.
|
||||||
|
/// </summary>
|
||||||
|
MentorPvE = 79,
|
||||||
|
|
||||||
FateSpecial2,
|
/// <summary>
|
||||||
|
/// The crafting mentor icon.
|
||||||
|
/// </summary>
|
||||||
|
MentorCrafting = 80,
|
||||||
|
|
||||||
PriorityWorld,
|
/// <summary>
|
||||||
|
/// The PvP mentor icon.
|
||||||
|
/// </summary>
|
||||||
|
MentorPvP = 81,
|
||||||
|
|
||||||
ElementalLevel,
|
/// <summary>
|
||||||
ExclamationRectangle,
|
/// The tank role icon.
|
||||||
|
/// </summary>
|
||||||
|
Tank = 82,
|
||||||
|
|
||||||
NotoriousMonster,
|
/// <summary>
|
||||||
|
/// The healer role icon.
|
||||||
|
/// </summary>
|
||||||
|
Healer = 83,
|
||||||
|
|
||||||
Recording,
|
/// <summary>
|
||||||
Alarm,
|
/// The DPS role icon.
|
||||||
|
/// </summary>
|
||||||
ArrowUp,
|
DPS = 84,
|
||||||
ArrowDown,
|
|
||||||
Crystarium,
|
|
||||||
|
|
||||||
MentorProblem,
|
|
||||||
|
|
||||||
FateUnknownGold,
|
/// <summary>
|
||||||
|
/// The crafter role icon.
|
||||||
|
/// </summary>
|
||||||
|
Crafter = 85,
|
||||||
|
|
||||||
OrangeDiamond,
|
/// <summary>
|
||||||
FateCrafting
|
/// The gatherer role icon.
|
||||||
|
/// </summary>
|
||||||
|
Gatherer = 86,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "any" role icon.
|
||||||
|
/// </summary>
|
||||||
|
AnyClass = 87,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cross-world icon.
|
||||||
|
/// </summary>
|
||||||
|
CrossWorld = 88,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The slay type Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateSlay = 89,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The boss type Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateBoss = 90,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gather type Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateGather = 91,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The defend type Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateDefend = 92,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The escort type Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateEscort = 93,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The special type 1 Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateSpecial1 = 94,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The returner icon.
|
||||||
|
/// </summary>
|
||||||
|
Returner = 95,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Far-East region icon.
|
||||||
|
/// </summary>
|
||||||
|
FarEast = 96,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Gyr Albania region icon.
|
||||||
|
/// </summary>
|
||||||
|
GyrAbania = 97,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The special type 2 Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateSpecial2 = 98,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The priority world icon.
|
||||||
|
/// </summary>
|
||||||
|
PriorityWorld = 99,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The elemental level icon.
|
||||||
|
/// </summary>
|
||||||
|
ElementalLevel = 100,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exclamation rectangle icon.
|
||||||
|
/// </summary>
|
||||||
|
ExclamationRectangle = 101,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The notorious monster icon.
|
||||||
|
/// </summary>
|
||||||
|
NotoriousMonster = 102,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The recording icon.
|
||||||
|
/// </summary>
|
||||||
|
Recording = 103,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The alarm icon.
|
||||||
|
/// </summary>
|
||||||
|
Alarm = 104,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The arrow up icon.
|
||||||
|
/// </summary>
|
||||||
|
ArrowUp = 105,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The arrow down icon.
|
||||||
|
/// </summary>
|
||||||
|
ArrowDown = 106,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Crystarium region icon.
|
||||||
|
/// </summary>
|
||||||
|
Crystarium = 107,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mentor problem icon.
|
||||||
|
/// </summary>
|
||||||
|
MentorProblem = 108,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The unknown gold type Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateUnknownGold = 109,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The orange diamond icon.
|
||||||
|
/// </summary>
|
||||||
|
OrangeDiamond = 110,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The crafting type Fate icon.
|
||||||
|
/// </summary>
|
||||||
|
FateCrafting = 111,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling
|
namespace Dalamud.Game.Text.SeStringHandling
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface binding for a payload that can provide readable Text.
|
||||||
|
/// </summary>
|
||||||
public interface ITextProvider
|
public interface ITextProvider
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the readable text.
|
||||||
|
/// </summary>
|
||||||
string Text { get; }
|
string Text { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -19,35 +19,8 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a parsed SeString payload.
|
/// This class represents a parsed SeString payload.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Payload
|
public abstract partial class Payload
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The type of this payload.
|
|
||||||
/// </summary>
|
|
||||||
public abstract PayloadType Type { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this payload has been modified since the last Encode().
|
|
||||||
/// </summary>
|
|
||||||
public bool Dirty { get; protected set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encodes the internal state of this payload into a byte[] suitable for sending to in-game
|
|
||||||
/// handlers such as the chat log.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Encoded binary payload data suitable for use with in-game handlers.</returns>
|
|
||||||
protected abstract byte[] EncodeImpl();
|
|
||||||
|
|
||||||
// TODO: endOfStream is somewhat legacy now that payload length is always handled correctly.
|
|
||||||
// This could be changed to just take a straight byte[], but that would complicate reading
|
|
||||||
// but we could probably at least remove the end param
|
|
||||||
/// <summary>
|
|
||||||
/// Decodes a byte stream from the game into a payload object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">A BinaryReader containing at least all the data for this payload.</param>
|
|
||||||
/// <param name="endOfStream">The location holding the end of the data for this payload.</param>
|
|
||||||
protected abstract void DecodeImpl(BinaryReader reader, long endOfStream);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Lumina instance to use for any necessary data lookups.
|
/// The Lumina instance to use for any necessary data lookups.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -58,31 +31,26 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
private byte[] encodedData;
|
private byte[] encodedData;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encode this payload object into a byte[] useable in-game for things like the chat log.
|
/// Gets the type of this payload.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="force">If true, ignores any cached value and forcibly reencodes the payload from its internal representation.</param>
|
public abstract PayloadType Type { get; }
|
||||||
/// <returns>A byte[] suitable for use with in-game handlers such as the chat log.</returns>
|
|
||||||
public byte[] Encode(bool force = false)
|
|
||||||
{
|
|
||||||
if (Dirty || force)
|
|
||||||
{
|
|
||||||
this.encodedData = EncodeImpl();
|
|
||||||
Dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.encodedData;
|
/// <summary>
|
||||||
}
|
/// Gets or sets a value indicating whether whether this payload has been modified since the last Encode().
|
||||||
|
/// </summary>
|
||||||
|
public bool Dirty { get; protected set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Decodes a binary representation of a payload into its corresponding nice object payload.
|
/// Decodes a binary representation of a payload into its corresponding nice object payload.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">A reader positioned at the start of the payload, and containing at least one entire payload.</param>
|
/// <param name="reader">A reader positioned at the start of the payload, and containing at least one entire payload.</param>
|
||||||
|
/// <param name="data">The DataManager instance.</param>
|
||||||
/// <returns>The constructed Payload-derived object that was decoded from the binary data.</returns>
|
/// <returns>The constructed Payload-derived object that was decoded from the binary data.</returns>
|
||||||
public static Payload Decode(BinaryReader reader, DataManager data)
|
public static Payload Decode(BinaryReader reader, DataManager data)
|
||||||
{
|
{
|
||||||
var payloadStartPos = reader.BaseStream.Position;
|
var payloadStartPos = reader.BaseStream.Position;
|
||||||
|
|
||||||
Payload payload = null;
|
Payload payload;
|
||||||
|
|
||||||
var initialByte = reader.ReadByte();
|
var initialByte = reader.ReadByte();
|
||||||
reader.BaseStream.Position--;
|
reader.BaseStream.Position--;
|
||||||
|
|
@ -113,6 +81,39 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encode this payload object into a byte[] useable in-game for things like the chat log.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="force">If true, ignores any cached value and forcibly reencodes the payload from its internal representation.</param>
|
||||||
|
/// <returns>A byte[] suitable for use with in-game handlers such as the chat log.</returns>
|
||||||
|
public byte[] Encode(bool force = false)
|
||||||
|
{
|
||||||
|
if (this.Dirty || force)
|
||||||
|
{
|
||||||
|
this.encodedData = this.EncodeImpl();
|
||||||
|
this.Dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.encodedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes the internal state of this payload into a byte[] suitable for sending to in-game
|
||||||
|
/// handlers such as the chat log.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Encoded binary payload data suitable for use with in-game handlers.</returns>
|
||||||
|
protected abstract byte[] EncodeImpl();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes a byte stream from the game into a payload object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">A BinaryReader containing at least all the data for this payload.</param>
|
||||||
|
/// <param name="endOfStream">The location holding the end of the data for this payload.</param>
|
||||||
|
// TODO: endOfStream is somewhat legacy now that payload length is always handled correctly.
|
||||||
|
// This could be changed to just take a straight byte[], but that would complicate reading
|
||||||
|
// but we could probably at least remove the end param
|
||||||
|
protected abstract void DecodeImpl(BinaryReader reader, long endOfStream);
|
||||||
|
|
||||||
private static Payload DecodeChunk(BinaryReader reader)
|
private static Payload DecodeChunk(BinaryReader reader)
|
||||||
{
|
{
|
||||||
Payload payload = null;
|
Payload payload = null;
|
||||||
|
|
@ -164,18 +165,20 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EmbeddedInfoType.LinkTerminator:
|
case EmbeddedInfoType.LinkTerminator:
|
||||||
// this has no custom handling and so needs to fallthrough to ensure it is captured
|
// this has no custom handling and so needs to fallthrough to ensure it is captured
|
||||||
default:
|
default:
|
||||||
// but I'm also tired of this log
|
// but I'm also tired of this log
|
||||||
if (subType != EmbeddedInfoType.LinkTerminator)
|
if (subType != EmbeddedInfoType.LinkTerminator)
|
||||||
{
|
{
|
||||||
Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType);
|
Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewind so we capture the Interactable byte in the raw data
|
// rewind so we capture the Interactable byte in the raw data
|
||||||
reader.BaseStream.Seek(-1, SeekOrigin.Current);
|
reader.BaseStream.Seek(-1, SeekOrigin.Current);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SeStringChunkType.AutoTranslateKey:
|
case SeStringChunkType.AutoTranslateKey:
|
||||||
|
|
@ -216,43 +219,126 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region parse constants and helpers
|
/// <summary>
|
||||||
|
/// Parsing helpers.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class Payload
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The start byte of a payload.
|
||||||
|
/// </summary>
|
||||||
protected const byte START_BYTE = 0x02;
|
protected const byte START_BYTE = 0x02;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The end byte of a payload.
|
||||||
|
/// </summary>
|
||||||
protected const byte END_BYTE = 0x03;
|
protected const byte END_BYTE = 0x03;
|
||||||
|
|
||||||
protected enum SeStringChunkType
|
/// <summary>
|
||||||
{
|
/// This represents the type of embedded info in a payload.
|
||||||
Icon = 0x12,
|
/// </summary>
|
||||||
EmphasisItalic = 0x1A,
|
|
||||||
SeHyphen = 0x1F,
|
|
||||||
Interactable = 0x27,
|
|
||||||
AutoTranslateKey = 0x2E,
|
|
||||||
UIForeground = 0x48,
|
|
||||||
UIGlow = 0x49
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum EmbeddedInfoType
|
public enum EmbeddedInfoType
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A player's name.
|
||||||
|
/// </summary>
|
||||||
PlayerName = 0x01,
|
PlayerName = 0x01,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The link to an iteme.
|
||||||
|
/// </summary>
|
||||||
ItemLink = 0x03,
|
ItemLink = 0x03,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The link to a map position.
|
||||||
|
/// </summary>
|
||||||
MapPositionLink = 0x04,
|
MapPositionLink = 0x04,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The link to a quest.
|
||||||
|
/// </summary>
|
||||||
QuestLink = 0x05,
|
QuestLink = 0x05,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A status effect.
|
||||||
|
/// </summary>
|
||||||
Status = 0x09,
|
Status = 0x09,
|
||||||
|
|
||||||
DalamudLink = 0x0F, // Dalamud Custom
|
/// <summary>
|
||||||
|
/// A custom Dalamud link.
|
||||||
|
/// </summary>
|
||||||
|
DalamudLink = 0x0F,
|
||||||
|
|
||||||
LinkTerminator = 0xCF // not clear but seems to always follow a link
|
/// <summary>
|
||||||
|
/// A link terminator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It is not exactly clear what this is, but seems to always follow a link.
|
||||||
|
/// </remarks>
|
||||||
|
LinkTerminator = 0xCF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This represents the type of payload and how it should be encoded.
|
||||||
|
/// </summary>
|
||||||
|
protected enum SeStringChunkType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// See the <see cref="IconPayload"/> class.
|
||||||
|
/// </summary>
|
||||||
|
Icon = 0x12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See the <see cref="EmphasisItalicPayload"/> class.
|
||||||
|
/// </summary>
|
||||||
|
EmphasisItalic = 0x1A,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See the <see cref="SeHyphenPayload"/> class.
|
||||||
|
/// </summary>
|
||||||
|
SeHyphen = 0x1F,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See any of the link-type classes:
|
||||||
|
/// <see cref="PlayerPayload"/>,
|
||||||
|
/// <see cref="ItemPayload"/>,
|
||||||
|
/// <see cref="MapLinkPayload"/>,
|
||||||
|
/// <see cref="StatusPayload"/>,
|
||||||
|
/// <see cref="QuestPayload"/>,
|
||||||
|
/// <see cref="DalamudLinkPayload"/>.
|
||||||
|
/// </summary>
|
||||||
|
Interactable = 0x27,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See the <see cref="AutoTranslatePayload"/> class.
|
||||||
|
/// </summary>
|
||||||
|
AutoTranslateKey = 0x2E,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See the <see cref="UIForegroundPayload"/> class.
|
||||||
|
/// </summary>
|
||||||
|
UIForeground = 0x48,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See the <see cref="UIGlowPayload"/> class.
|
||||||
|
/// </summary>
|
||||||
|
UIGlow = 0x49,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the packed integer from SE's native data format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The BinaryReader instance.</param>
|
||||||
|
/// <returns>An integer.</returns>
|
||||||
// made protected, unless we actually want to use it externally
|
// made protected, unless we actually want to use it externally
|
||||||
// in which case it should probably go live somewhere else
|
// in which case it should probably go live somewhere else
|
||||||
protected static uint GetInteger(BinaryReader input)
|
protected static uint GetInteger(BinaryReader input)
|
||||||
{
|
{
|
||||||
uint marker = input.ReadByte();
|
uint marker = input.ReadByte();
|
||||||
if (marker < 0xD0) return marker - 1;
|
if (marker < 0xD0)
|
||||||
|
return marker - 1;
|
||||||
|
|
||||||
// the game adds 0xF0 marker for values >= 0xCF
|
// the game adds 0xF0 marker for values >= 0xCF
|
||||||
// uasge of 0xD0-0xEF is unknown, should we throw here?
|
// uasge of 0xD0-0xEF is unknown, should we throw here?
|
||||||
|
|
@ -269,6 +355,11 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
return BitConverter.ToUInt32(ret, 0);
|
return BitConverter.ToUInt32(ret, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a packed integer in Se's native data format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to pack.</param>
|
||||||
|
/// <returns>A packed integer.</returns>
|
||||||
protected static byte[] MakeInteger(uint value)
|
protected static byte[] MakeInteger(uint value)
|
||||||
{
|
{
|
||||||
if (value < 0xCF)
|
if (value < 0xCF)
|
||||||
|
|
@ -287,22 +378,33 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
ret[0] |= (byte)(1 << i);
|
ret[0] |= (byte)(1 << i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret[0] -= 1;
|
ret[0] -= 1;
|
||||||
|
|
||||||
return ret.ToArray();
|
return ret.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static (uint, uint) GetPackedIntegers(BinaryReader input)
|
/// <summary>
|
||||||
|
/// From a binary packed integer, get the high and low bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The BinaryReader instance.</param>
|
||||||
|
/// <returns>The high and low bytes.</returns>
|
||||||
|
protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input)
|
||||||
{
|
{
|
||||||
var value = GetInteger(input);
|
var value = GetInteger(input);
|
||||||
return (value >> 16, value & 0xFFFF);
|
return (value >> 16, value & 0xFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static byte[] MakePackedInteger(uint val1, uint val2)
|
/// <summary>
|
||||||
|
/// Create a packed integer from the given high and low bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="high">The high order bytes.</param>
|
||||||
|
/// <param name="low">The low order bytes.</param>
|
||||||
|
/// <returns>A packed integer.</returns>
|
||||||
|
protected static byte[] MakePackedInteger(uint high, uint low)
|
||||||
{
|
{
|
||||||
var value = (val1 << 16) | (val2 & 0xFFFF);
|
var value = (high << 16) | (low & 0xFFFF);
|
||||||
return MakeInteger(value);
|
return MakeInteger(value);
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling
|
namespace Dalamud.Game.Text.SeStringHandling
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -10,54 +9,70 @@ namespace Dalamud.Game.Text.SeStringHandling
|
||||||
/// An SeString payload representing a player link.
|
/// An SeString payload representing a player link.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Player,
|
Player,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing an Item link.
|
/// An SeString payload representing an Item link.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Item,
|
Item,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing an Status Effect link.
|
/// An SeString payload representing an Status Effect link.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Status,
|
Status,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing raw, typed text.
|
/// An SeString payload representing raw, typed text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RawText,
|
RawText,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing a text foreground color.
|
/// An SeString payload representing a text foreground color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UIForeground,
|
UIForeground,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing a text glow color.
|
/// An SeString payload representing a text glow color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UIGlow,
|
UIGlow,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing a map position link, such as from <flag> or <pos>.
|
/// An SeString payload representing a map position link, such as from <flag> or <pos>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MapLink,
|
MapLink,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing an auto-translate dictionary entry.
|
/// An SeString payload representing an auto-translate dictionary entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoTranslateText,
|
AutoTranslateText,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing italic emphasis formatting on text.
|
/// An SeString payload representing italic emphasis formatting on text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EmphasisItalic,
|
EmphasisItalic,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing a bitmap icon.
|
/// An SeString payload representing a bitmap icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Icon,
|
Icon,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A SeString payload representing a quest link.
|
/// A SeString payload representing a quest link.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Quest,
|
Quest,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A SeString payload representing a custom clickable link for dalamud plugins
|
/// A SeString payload representing a custom clickable link for dalamud plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DalamudLink,
|
DalamudLink,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An SeString payload representing any data we don't handle.
|
/// An SeString payload representing any data we don't handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An SeString payload representing a doublewide SE hypen.
|
||||||
|
/// </summary>
|
||||||
SeHyphen,
|
SeHyphen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Serilog;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
{
|
{
|
||||||
|
|
@ -14,25 +15,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AutoTranslatePayload : Payload, ITextProvider
|
public class AutoTranslatePayload : Payload, ITextProvider
|
||||||
{
|
{
|
||||||
public override PayloadType Type => PayloadType.AutoTranslateText;
|
|
||||||
|
|
||||||
private string text;
|
private string text;
|
||||||
/// <summary>
|
|
||||||
/// The actual text displayed in-game for this payload.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Value is evaluated lazily and cached.
|
|
||||||
/// </remarks>
|
|
||||||
public string Text
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// wrap the text in the colored brackets that is uses in-game, since those
|
|
||||||
// are not actually part of any of the payloads
|
|
||||||
this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {Resolve()} {(char)SeIconChar.AutoTranslateClose}";
|
|
||||||
return this.text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
private uint group;
|
private uint group;
|
||||||
|
|
@ -40,9 +23,8 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
private uint key;
|
private uint key;
|
||||||
|
|
||||||
internal AutoTranslatePayload() { }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||||
/// Creates a new auto-translate payload.
|
/// Creates a new auto-translate payload.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">DataManager instance needed to resolve game data.</param>
|
/// <param name="data">DataManager instance needed to resolve game data.</param>
|
||||||
|
|
@ -52,19 +34,49 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
/// This table is somewhat complicated in structure, and so using this constructor may not be very nice.
|
/// This table is somewhat complicated in structure, and so using this constructor may not be very nice.
|
||||||
/// There is probably little use to create one of these, however.
|
/// There is probably little use to create one of these, however.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public AutoTranslatePayload(DataManager data, uint group, uint key) {
|
public AutoTranslatePayload(DataManager data, uint group, uint key)
|
||||||
|
{
|
||||||
|
// TODO: friendlier ctor? not sure how to handle that given how weird the tables are
|
||||||
this.DataResolver = data;
|
this.DataResolver = data;
|
||||||
this.group = group;
|
this.group = group;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: friendlier ctor? not sure how to handle that given how weird the tables are
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||||
public override string ToString()
|
/// </summary>
|
||||||
|
internal AutoTranslatePayload()
|
||||||
{
|
{
|
||||||
return $"{Type} - Group: {group}, Key: {key}, Text: {Text}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override PayloadType Type => PayloadType.AutoTranslateText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the actual text displayed in-game for this payload.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Value is evaluated lazily and cached.
|
||||||
|
/// </remarks>
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads
|
||||||
|
return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string that represents the current object.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string that represents the current object.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
protected override byte[] EncodeImpl()
|
protected override byte[] EncodeImpl()
|
||||||
{
|
{
|
||||||
var keyBytes = MakeInteger(this.key);
|
var keyBytes = MakeInteger(this.key);
|
||||||
|
|
@ -74,7 +86,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
{
|
{
|
||||||
START_BYTE,
|
START_BYTE,
|
||||||
(byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen,
|
(byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen,
|
||||||
(byte)this.group
|
(byte)this.group,
|
||||||
};
|
};
|
||||||
bytes.AddRange(keyBytes);
|
bytes.AddRange(keyBytes);
|
||||||
bytes.Add(END_BYTE);
|
bytes.Add(END_BYTE);
|
||||||
|
|
@ -82,6 +94,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
return bytes.ToArray();
|
return bytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||||
{
|
{
|
||||||
// this seems to always be a bare byte, and not following normal integer encoding
|
// this seems to always be a bare byte, and not following normal integer encoding
|
||||||
|
|
@ -105,7 +118,9 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
// (again, if it's meant for another table)
|
// (again, if it's meant for another table)
|
||||||
row = sheet.GetRow(this.key);
|
row = sheet.GetRow(this.key);
|
||||||
}
|
}
|
||||||
catch { } // don't care, row will be null
|
catch
|
||||||
|
{
|
||||||
|
} // don't care, row will be null
|
||||||
|
|
||||||
if (row?.Group == this.group)
|
if (row?.Group == this.group)
|
||||||
{
|
{
|
||||||
|
|
@ -142,7 +157,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
"TextCommand" => this.DataResolver.GetExcelSheet<TextCommand>().GetRow(this.key).Command,
|
"TextCommand" => this.DataResolver.GetExcelSheet<TextCommand>().GetRow(this.key).Command,
|
||||||
"Tribe" => this.DataResolver.GetExcelSheet<Tribe>().GetRow(this.key).Masculine,
|
"Tribe" => this.DataResolver.GetExcelSheet<Tribe>().GetRow(this.key).Masculine,
|
||||||
"Weather" => this.DataResolver.GetExcelSheet<Weather>().GetRow(this.key).Name,
|
"Weather" => this.DataResolver.GetExcelSheet<Weather>().GetRow(this.key).Name,
|
||||||
_ => throw new Exception(actualTableName)
|
_ => throw new Exception(actualTableName),
|
||||||
};
|
};
|
||||||
|
|
||||||
value = name;
|
value = name;
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,62 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads {
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// This class represents a custom Dalamud clickable chat link.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DalamudLinkPayload : Payload {
|
public class DalamudLinkPayload : Payload
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
public override PayloadType Type => PayloadType.DalamudLink;
|
public override PayloadType Type => PayloadType.DalamudLink;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the plugin command ID to be linked.
|
||||||
|
/// </summary>
|
||||||
public uint CommandId { get; internal set; } = 0;
|
public uint CommandId { get; internal set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the plugin name to be linked.
|
||||||
|
/// </summary>
|
||||||
[NotNull]
|
[NotNull]
|
||||||
public string Plugin { get; internal set; } = string.Empty;
|
public string Plugin { get; internal set; } = string.Empty;
|
||||||
|
|
||||||
protected override byte[] EncodeImpl() {
|
/// <inheritdoc/>
|
||||||
var pluginBytes = Encoding.UTF8.GetBytes(Plugin);
|
public override string ToString()
|
||||||
var commandBytes = MakeInteger(CommandId);
|
{
|
||||||
|
return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override byte[] EncodeImpl()
|
||||||
|
{
|
||||||
|
var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin);
|
||||||
|
var commandBytes = MakeInteger(this.CommandId);
|
||||||
var chunkLen = 3 + pluginBytes.Length + commandBytes.Length;
|
var chunkLen = 3 + pluginBytes.Length + commandBytes.Length;
|
||||||
|
|
||||||
if (chunkLen > 255) {
|
if (chunkLen > 255)
|
||||||
|
{
|
||||||
throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload");
|
throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload");
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = new List<byte> {START_BYTE, (byte) SeStringChunkType.Interactable, (byte) chunkLen, (byte) EmbeddedInfoType.DalamudLink};
|
var bytes = new List<byte> { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink };
|
||||||
bytes.Add((byte) pluginBytes.Length);
|
bytes.Add((byte)pluginBytes.Length);
|
||||||
bytes.AddRange(pluginBytes);
|
bytes.AddRange(pluginBytes);
|
||||||
bytes.AddRange(commandBytes);
|
bytes.AddRange(commandBytes);
|
||||||
bytes.Add(END_BYTE);
|
bytes.Add(END_BYTE);
|
||||||
return bytes.ToArray();
|
return bytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream) {
|
/// <inheritdoc/>
|
||||||
Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte()));
|
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||||
CommandId = GetInteger(reader);
|
{
|
||||||
}
|
this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte()));
|
||||||
|
this.CommandId = GetInteger(reader);
|
||||||
public override string ToString() {
|
|
||||||
return $"{Type} - Plugin: {Plugin}, Command: {CommandId}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,47 +14,58 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
public class EmphasisItalicPayload : Payload
|
public class EmphasisItalicPayload : Payload
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Payload representing enabling italics on following text.
|
/// Initializes a new instance of the <see cref="EmphasisItalicPayload"/> class.
|
||||||
/// </summary>
|
|
||||||
public static EmphasisItalicPayload ItalicsOn => new EmphasisItalicPayload(true);
|
|
||||||
/// <summary>
|
|
||||||
/// Payload representing disabling italics on following text.
|
|
||||||
/// </summary>
|
|
||||||
public static EmphasisItalicPayload ItalicsOff => new EmphasisItalicPayload(false);
|
|
||||||
|
|
||||||
public override PayloadType Type => PayloadType.EmphasisItalic;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this payload enables italics formatting for following text.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled { get; private set; }
|
|
||||||
|
|
||||||
internal EmphasisItalicPayload() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an EmphasisItalicPayload.
|
/// Creates an EmphasisItalicPayload.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="enabled">Whether italics formatting should be enabled or disabled for following text.</param>
|
/// <param name="enabled">Whether italics formatting should be enabled or disabled for following text.</param>
|
||||||
public EmphasisItalicPayload(bool enabled)
|
public EmphasisItalicPayload(bool enabled)
|
||||||
{
|
{
|
||||||
IsEnabled = enabled;
|
this.IsEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="EmphasisItalicPayload"/> class.
|
||||||
|
/// Creates an EmphasisItalicPayload.
|
||||||
|
/// </summary>
|
||||||
|
internal EmphasisItalicPayload()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a payload representing enabling italics on following text.
|
||||||
|
/// </summary>
|
||||||
|
public static EmphasisItalicPayload ItalicsOn => new(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a payload representing disabling italics on following text.
|
||||||
|
/// </summary>
|
||||||
|
public static EmphasisItalicPayload ItalicsOff => new(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this payload enables italics formatting for following text.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override PayloadType Type => PayloadType.EmphasisItalic;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{Type} - Enabled: {IsEnabled}";
|
return $"{this.Type} - Enabled: {this.IsEnabled}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
protected override byte[] EncodeImpl()
|
protected override byte[] EncodeImpl()
|
||||||
{
|
{
|
||||||
// realistically this will always be a single byte of value 1 or 2
|
// realistically this will always be a single byte of value 1 or 2
|
||||||
// but we'll treat it normally anyway
|
// but we'll treat it normally anyway
|
||||||
var enabledBytes = MakeInteger(IsEnabled ? (uint)1 : 0);
|
var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0);
|
||||||
|
|
||||||
var chunkLen = enabledBytes.Length + 1;
|
var chunkLen = enabledBytes.Length + 1;
|
||||||
var bytes = new List<byte>()
|
var bytes = new List<byte>()
|
||||||
{
|
{
|
||||||
START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen
|
START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen,
|
||||||
};
|
};
|
||||||
bytes.AddRange(enabledBytes);
|
bytes.AddRange(enabledBytes);
|
||||||
bytes.Add(END_BYTE);
|
bytes.Add(END_BYTE);
|
||||||
|
|
@ -62,9 +73,10 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
return bytes.ToArray();
|
return bytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||||
{
|
{
|
||||||
IsEnabled = (GetInteger(reader) == 1);
|
this.IsEnabled = GetInteger(reader) == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,71 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads {
|
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SeString payload representing a bitmap icon from fontIcon
|
/// SeString payload representing a bitmap icon from fontIcon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IconPayload : Payload {
|
public class IconPayload : Payload
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="IconPayload"/> class.
|
||||||
|
/// Create a Icon payload for the specified icon.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="icon">The Icon.</param>
|
||||||
|
public IconPayload(BitmapFontIcon icon)
|
||||||
|
{
|
||||||
|
this.Icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Index of the icon
|
/// Initializes a new instance of the <see cref="IconPayload"/> class.
|
||||||
|
/// Create a Icon payload for the specified icon.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iconIndex">Index of the icon.</param>
|
||||||
|
[Obsolete("IconPayload(uint) is deprecated, please use IconPayload(BitmapFontIcon).")]
|
||||||
|
public IconPayload(uint iconIndex)
|
||||||
|
: this((BitmapFontIcon)iconIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="IconPayload"/> class.
|
||||||
|
/// Create a Icon payload for the specified icon.
|
||||||
|
/// </summary>
|
||||||
|
internal IconPayload()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override PayloadType Type => PayloadType.Icon;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Use IconPayload.Icon")]
|
[Obsolete("Use IconPayload.Icon")]
|
||||||
public uint IconIndex => (uint) Icon;
|
public uint IconIndex => (uint)this.Icon;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Icon the payload represents.
|
/// Gets or sets the icon the payload represents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None;
|
public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None;
|
||||||
|
|
||||||
internal IconPayload() { }
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
/// <summary>
|
{
|
||||||
/// Create a Icon payload for the specified icon.
|
return $"{this.Type} - {this.Icon}";
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconIndex">Index of the icon</param>
|
|
||||||
[Obsolete("IconPayload(uint) is deprecated, please use IconPayload(BitmapFontIcon).")]
|
|
||||||
public IconPayload(uint iconIndex) : this((BitmapFontIcon) iconIndex) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a Icon payload for the specified icon.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="icon">The Icon</param>
|
|
||||||
public IconPayload(BitmapFontIcon icon) {
|
|
||||||
Icon = icon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override PayloadType Type => PayloadType.Icon;
|
protected override byte[] EncodeImpl()
|
||||||
|
{
|
||||||
/// <inheritdoc />
|
var indexBytes = MakeInteger((uint)this.Icon);
|
||||||
protected override byte[] EncodeImpl() {
|
|
||||||
var indexBytes = MakeInteger((uint) this.Icon);
|
|
||||||
var chunkLen = indexBytes.Length + 1;
|
var chunkLen = indexBytes.Length + 1;
|
||||||
var bytes = new List<byte>(new byte[] {
|
var bytes = new List<byte>(new byte[]
|
||||||
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen
|
{
|
||||||
|
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen,
|
||||||
});
|
});
|
||||||
bytes.AddRange(indexBytes);
|
bytes.AddRange(indexBytes);
|
||||||
bytes.Add(END_BYTE);
|
bytes.Add(END_BYTE);
|
||||||
|
|
@ -53,14 +73,9 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream) {
|
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||||
Icon = (BitmapFontIcon) GetInteger(reader);
|
{
|
||||||
|
this.Icon = (BitmapFontIcon)GetInteger(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString() {
|
|
||||||
return $"{Type} - {Icon}";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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