mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 14:23: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,8 +55,7 @@ namespace Dalamud.Configuration
|
|||
File.ReadAllText(path.FullName),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
TypeNameAssemblyFormatHandling =
|
||||
TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameHandling = TypeNameHandling.Objects,
|
||||
});
|
||||
}
|
||||
|
|
@ -108,8 +107,8 @@ namespace Dalamud.Configuration
|
|||
/// </summary>
|
||||
/// <param name="pluginName">InternalName of the plugin.</param>
|
||||
/// <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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Addon;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Internal;
|
||||
using Dalamud.Game.Network;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using Serilog;
|
||||
|
|
@ -182,12 +183,15 @@ namespace Dalamud
|
|||
/// </summary>
|
||||
internal bool IsReady { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin system is loaded.
|
||||
/// </summary>
|
||||
internal bool IsLoadedPluginSystem => this.PluginManager != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets location of stored assets.
|
||||
/// </summary>
|
||||
internal DirectoryInfo AssetDirectory => new DirectoryInfo(this.StartInfo.AssetDirectory);
|
||||
internal DirectoryInfo AssetDirectory => new(this.StartInfo.AssetDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// Runs tier 1 of the Dalamud initialization process.
|
||||
|
|
@ -231,7 +235,6 @@ namespace Dalamud
|
|||
|
||||
Log.Information("[T2] AntiDebug OK!");
|
||||
|
||||
|
||||
this.WinSock2 = new WinSockHandlers();
|
||||
|
||||
Log.Information("[T2] WinSock OK!");
|
||||
|
|
@ -457,8 +460,7 @@ namespace Dalamud
|
|||
/// </summary>
|
||||
internal void ReplaceExceptionHandler()
|
||||
{
|
||||
var releaseFilter = this.SigScanner.ScanText(
|
||||
"40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??");
|
||||
var releaseFilter = this.SigScanner.ScanText("40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??");
|
||||
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
||||
|
||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
|
|
@ -34,6 +34,18 @@
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</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>
|
||||
<None Remove="Resources\Lumina.Generated.dll" />
|
||||
<None Remove="stylecop.json" />
|
||||
|
|
@ -54,7 +66,7 @@
|
|||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||
<PackageReference Include="EasyHook" Version="2.7.6270" />
|
||||
<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>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
|
||||
namespace Dalamud
|
||||
{
|
||||
|
|
@ -50,5 +49,3 @@ namespace Dalamud
|
|||
public bool OptOutMbCollection;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore SA1401 // Fields should be private
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Data.LuminaExtensions;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiScene;
|
||||
|
|
@ -22,8 +23,8 @@ namespace Dalamud.Data
|
|||
/// </summary>
|
||||
public class DataManager : IDisposable
|
||||
{
|
||||
private readonly InterfaceManager interfaceManager;
|
||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||
private readonly InterfaceManager interfaceManager;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
this.interfaceManager = interfaceManager;
|
||||
|
|
@ -71,87 +73,6 @@ namespace Dalamud.Data
|
|||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -172,12 +93,13 @@ namespace Dalamud.Data
|
|||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||
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.English => Lumina.Data.Language.English,
|
||||
ClientLanguage.German => Lumina.Data.Language.German,
|
||||
ClientLanguage.French => Lumina.Data.Language.French,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), @"Unknown Language: " + this.Language)
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
|
||||
};
|
||||
return this.Excel.GetSheet<T>(lang);
|
||||
}
|
||||
|
|
@ -203,7 +125,7 @@ namespace Dalamud.Data
|
|||
var filePath = GameData.ParseFilePath(path);
|
||||
if (filePath == null)
|
||||
return default;
|
||||
return this.gameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile<T>(filePath.Category, filePath) : default(T);
|
||||
return this.gameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile<T>(filePath.Category, filePath) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -234,12 +156,13 @@ namespace Dalamud.Data
|
|||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile GetIcon(ClientLanguage iconLanguage, int iconId)
|
||||
{
|
||||
var type = iconLanguage switch {
|
||||
var type = iconLanguage switch
|
||||
{
|
||||
ClientLanguage.Japanese => "ja/",
|
||||
ClientLanguage.English => "en/",
|
||||
ClientLanguage.German => "de/",
|
||||
ClientLanguage.French => "fr/",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), @"Unknown Language: " + this.Language)
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
|
||||
};
|
||||
|
||||
return this.GetIcon(type, iconId);
|
||||
|
|
@ -273,15 +196,16 @@ namespace Dalamud.Data
|
|||
/// </summary>
|
||||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
public TextureWrap GetImGuiTexture(TexFile tex) =>
|
||||
this.interfaceManager.LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||
public TextureWrap GetImGuiTexture(TexFile tex)
|
||||
=> this.interfaceManager.LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||
|
||||
/// <summary>
|
||||
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
||||
/// </summary>
|
||||
/// <param name="path">The internal path to the texture.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
public TextureWrap GetImGuiTexture(string path) => this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
||||
public TextureWrap GetImGuiTexture(string path)
|
||||
=> this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
||||
|
||||
/// <summary>
|
||||
/// 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="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap GetImGuiTextureIcon(ClientLanguage iconLanguage, int iconId) =>
|
||||
this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
||||
public TextureWrap GetImGuiTextureIcon(ClientLanguage iconLanguage, int iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
||||
|
||||
/// <summary>
|
||||
/// 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="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap GetImGuiTextureIcon(string type, int iconId) =>
|
||||
this.GetImGuiTexture(this.GetIcon(type, iconId));
|
||||
public TextureWrap GetImGuiTextureIcon(string type, int iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -310,5 +234,83 @@ namespace Dalamud.Data
|
|||
{
|
||||
this.luminaResourceThread.Abort();
|
||||
}
|
||||
|
||||
/// <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
|
||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||
|
|
@ -95,7 +95,7 @@ namespace Dalamud
|
|||
|
||||
var newLogger = new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
.WriteTo.EventSink()
|
||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,24 +10,15 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
|||
|
||||
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 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 delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes);
|
||||
|
||||
private AtkValueSetString atkValueSetString;
|
||||
|
||||
private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
|
||||
|
||||
private Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
|
||||
// TODO: Make this into events in Framework.Gui
|
||||
private Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
|
||||
|
||||
|
|
@ -55,14 +46,24 @@ namespace Dalamud.Game.Addon
|
|||
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
|
||||
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetString>(atkValueSetStringAddress);
|
||||
|
||||
var uiModuleRequestMainCommandAddress = this.dalamud.SigScanner.ScanText(
|
||||
"40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
||||
var uiModuleRequestMainCommandAddress = this.dalamud.SigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
||||
this.hookUiModuleRequestMainCommand = new Hook<UiModuleRequestMainCommand>(
|
||||
uiModuleRequestMainCommandAddress,
|
||||
new UiModuleRequestMainCommand(this.UiModuleRequestMainCommandDetour),
|
||||
this);
|
||||
}
|
||||
|
||||
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||
|
||||
private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type);
|
||||
|
||||
private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes);
|
||||
|
||||
private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
|
||||
|
||||
/// <summary>
|
||||
/// Enables the <see cref="DalamudSystemMenu"/>.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.hookAgentHudOpenSystemMenu.Enable();
|
||||
|
|
@ -95,7 +96,7 @@ namespace Dalamud.Game.Addon
|
|||
this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int
|
||||
this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int);
|
||||
|
||||
for (uint i = menuSize + 2; i > 1; i--)
|
||||
for (var i = menuSize + 2; i > 1; i--)
|
||||
{
|
||||
var curEntry = &atkValueArgs[i + 5 - 2];
|
||||
var nextEntry = &atkValueArgs[i + 5];
|
||||
|
|
@ -155,21 +156,44 @@ namespace Dalamud.Game.Addon
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
protected virtual void Dispose(bool disposing)
|
||||
/// <summary>
|
||||
/// Implements IDisposable.
|
||||
/// </summary>
|
||||
internal sealed partial class DalamudSystemMenu : IDisposable
|
||||
{
|
||||
if (!disposing) return;
|
||||
private bool disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// 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()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
this.disposed = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,111 +6,156 @@ using System.Reflection;
|
|||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using CheapLoc;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game {
|
||||
public class ChatHandlers {
|
||||
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new Dictionary<string, string> {
|
||||
{"", "<:ffxive071:585847382210642069>"},
|
||||
{"", "<:ffxive083:585848592699490329>"}
|
||||
namespace Dalamud.Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Chat events and public helper functions.
|
||||
/// </summary>
|
||||
public class ChatHandlers
|
||||
{
|
||||
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||
{
|
||||
{ "", "<:ffxive071:585847382210642069>" },
|
||||
{ "", "<:ffxive083:585848592699490329>" },
|
||||
};
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private DalamudLinkPayload openInstallerWindowLink;
|
||||
|
||||
private readonly Dictionary<XivChatType, Color> HandledChatTypeColors = new Dictionary<XivChatType, Color> {
|
||||
{XivChatType.CrossParty, Color.DodgerBlue},
|
||||
{XivChatType.Party, Color.DodgerBlue},
|
||||
{XivChatType.FreeCompany, Color.DeepSkyBlue},
|
||||
{XivChatType.CrossLinkShell1, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell2, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell3, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell4, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell5, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell6, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell7, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell8, Color.ForestGreen},
|
||||
{XivChatType.Ls1, Color.ForestGreen},
|
||||
{XivChatType.Ls2, Color.ForestGreen},
|
||||
{XivChatType.Ls3, Color.ForestGreen},
|
||||
{XivChatType.Ls4, Color.ForestGreen},
|
||||
{XivChatType.Ls5, Color.ForestGreen},
|
||||
{XivChatType.Ls6, Color.ForestGreen},
|
||||
{XivChatType.Ls7, Color.ForestGreen},
|
||||
{XivChatType.Ls8, Color.ForestGreen},
|
||||
{XivChatType.TellIncoming, Color.HotPink},
|
||||
{XivChatType.PvPTeam, Color.SandyBrown},
|
||||
{XivChatType.Urgent, Color.DarkViolet},
|
||||
{XivChatType.NoviceNetwork, Color.SaddleBrown},
|
||||
{XivChatType.Echo, Color.Gray}
|
||||
private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new()
|
||||
{
|
||||
{ XivChatType.CrossParty, Color.DodgerBlue },
|
||||
{ XivChatType.Party, Color.DodgerBlue },
|
||||
{ XivChatType.FreeCompany, Color.DeepSkyBlue },
|
||||
{ XivChatType.CrossLinkShell1, Color.ForestGreen },
|
||||
{ XivChatType.CrossLinkShell2, Color.ForestGreen },
|
||||
{ XivChatType.CrossLinkShell3, Color.ForestGreen },
|
||||
{ XivChatType.CrossLinkShell4, Color.ForestGreen },
|
||||
{ XivChatType.CrossLinkShell5, Color.ForestGreen },
|
||||
{ XivChatType.CrossLinkShell6, Color.ForestGreen },
|
||||
{ XivChatType.CrossLinkShell7, Color.ForestGreen },
|
||||
{ XivChatType.CrossLinkShell8, Color.ForestGreen },
|
||||
{ XivChatType.Ls1, Color.ForestGreen },
|
||||
{ XivChatType.Ls2, Color.ForestGreen },
|
||||
{ XivChatType.Ls3, Color.ForestGreen },
|
||||
{ XivChatType.Ls4, Color.ForestGreen },
|
||||
{ XivChatType.Ls5, Color.ForestGreen },
|
||||
{ XivChatType.Ls6, Color.ForestGreen },
|
||||
{ XivChatType.Ls7, Color.ForestGreen },
|
||||
{ XivChatType.Ls8, Color.ForestGreen },
|
||||
{ XivChatType.TellIncoming, Color.HotPink },
|
||||
{ XivChatType.PvPTeam, Color.SandyBrown },
|
||||
{ XivChatType.Urgent, Color.DarkViolet },
|
||||
{ XivChatType.NoviceNetwork, Color.SaddleBrown },
|
||||
{ XivChatType.Echo, Color.Gray },
|
||||
};
|
||||
|
||||
private readonly Regex rmtRegex =
|
||||
new Regex(
|
||||
private readonly Regex rmtRegex = new(
|
||||
@"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new Dictionary<ClientLanguage, Regex[]>() { {
|
||||
ClientLanguage.Japanese, new Regex[] {
|
||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
||||
{
|
||||
{
|
||||
ClientLanguage.Japanese,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled) }
|
||||
}, {
|
||||
ClientLanguage.English, new Regex[] {
|
||||
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled)
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", 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 (?:.+) 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)
|
||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.French,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
private readonly Regex urlRegex =
|
||||
new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?",
|
||||
RegexOptions.Compiled);
|
||||
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
private DalamudLinkPayload openInstallerWindowLink;
|
||||
private bool hasSeenLoadingMsg;
|
||||
|
||||
public string LastLink { get; private set; }
|
||||
|
||||
public ChatHandlers(Dalamud dalamud) {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dalamud">Dalamud instance.</param>
|
||||
public ChatHandlers(Dalamud dalamud)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
|
||||
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
|
||||
dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
|
||||
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.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||
{
|
||||
this.dalamud.DalamudUi.OpenPluginInstaller();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) {
|
||||
/// <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 matched = this.rmtRegex.IsMatch(textVal);
|
||||
if (matched) {
|
||||
if (matched)
|
||||
{
|
||||
// This seems to be a RMT ad - let's not show it
|
||||
Log.Debug("Handled RMT ad: " + message.TextValue);
|
||||
isHandled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.dalamud.Configuration.BadWords != null &&
|
||||
this.dalamud.Configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) {
|
||||
this.dalamud.Configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||
{
|
||||
// This seems to be in the user block list - let's not show it
|
||||
Log.Debug("Blocklist triggered");
|
||||
isHandled = true;
|
||||
|
|
@ -118,23 +163,24 @@ namespace Dalamud.Game {
|
|||
}
|
||||
}
|
||||
|
||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender,
|
||||
ref SeString message, ref bool isHandled) {
|
||||
|
||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
||||
PrintWelcomeMessage();
|
||||
this.PrintWelcomeMessage();
|
||||
|
||||
// For injections while logged in
|
||||
if (this.dalamud.ClientState.LocalPlayer != null && this.dalamud.ClientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||
PrintWelcomeMessage();
|
||||
this.PrintWelcomeMessage();
|
||||
|
||||
#if !DEBUG && false
|
||||
if (!this.hasSeenLoadingMsg)
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (type == XivChatType.RetainerSale) {
|
||||
foreach (var regex in retainerSaleRegexes[dalamud.StartInfo.Language]) {
|
||||
if (type == XivChatType.RetainerSale)
|
||||
{
|
||||
foreach (var regex in this.retainerSaleRegexes[this.dalamud.StartInfo.Language])
|
||||
{
|
||||
var matchInfo = regex.Match(message.TextValue);
|
||||
|
||||
// we no longer really need to do/validate the item matching since we read the id from the byte array
|
||||
|
|
@ -143,10 +189,9 @@ namespace Dalamud.Game {
|
|||
if (!itemInfo.Success)
|
||||
continue;
|
||||
|
||||
var itemLink =
|
||||
message.Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload;
|
||||
|
||||
if (itemLink == null) {
|
||||
var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
|
||||
if (itemLink == default)
|
||||
{
|
||||
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
|
||||
break;
|
||||
}
|
||||
|
|
@ -155,10 +200,10 @@ namespace Dalamud.Game {
|
|||
|
||||
var valueInfo = matchInfo.Groups["value"];
|
||||
// not sure if using a culture here would work correctly, so just strip symbols instead
|
||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue))
|
||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue))
|
||||
continue;
|
||||
|
||||
//Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
||||
// Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -168,7 +213,7 @@ namespace Dalamud.Game {
|
|||
|
||||
var linkMatch = this.urlRegex.Match(message.TextValue);
|
||||
if (linkMatch.Value.Length > 0)
|
||||
LastLink = linkMatch.Value;
|
||||
this.LastLink = linkMatch.Value;
|
||||
|
||||
// Handle all of this with SeString some day
|
||||
/*
|
||||
|
|
@ -193,43 +238,60 @@ namespace Dalamud.Game {
|
|||
*/
|
||||
}
|
||||
|
||||
private void PrintWelcomeMessage() {
|
||||
private void PrintWelcomeMessage()
|
||||
{
|
||||
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
||||
|
||||
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
|
||||
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), this.dalamud.PluginManager.Plugins.Count));
|
||||
|
||||
if (this.dalamud.Configuration.PrintPluginsWelcomeMsg) {
|
||||
foreach (var plugin in this.dalamud.PluginManager.Plugins.OrderBy(x => x.Plugin.Name)) {
|
||||
if (this.dalamud.Configuration.PrintPluginsWelcomeMsg)
|
||||
{
|
||||
foreach (var plugin in this.dalamud.PluginManager.Plugins.OrderBy(x => x.Plugin.Name))
|
||||
{
|
||||
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Plugin.Name, plugin.Definition.AssemblyVersion));
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion)) {
|
||||
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
||||
if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion))
|
||||
{
|
||||
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry
|
||||
{
|
||||
MessageBytes = Encoding.UTF8.GetBytes(Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog.")),
|
||||
Type = XivChatType.Notice
|
||||
Type = XivChatType.Notice,
|
||||
});
|
||||
|
||||
if (DalamudChangelogWindow.WarrantsChangelog)
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
this.dalamud.DalamudUi.OpenChangelog();
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
|
||||
this.dalamud.Configuration.LastVersion = assemblyVersion;
|
||||
this.dalamud.Configuration.Save();
|
||||
}
|
||||
|
||||
Task.Run(() => this.dalamud.PluginRepository.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins)).ContinueWith(t => {
|
||||
if (t.IsFaulted) {
|
||||
Task.Run(() => this.dalamud.PluginRepository.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins)).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Log.Error(t.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
var updatedPlugins = t.Result.UpdatedPlugins;
|
||||
|
||||
if (updatedPlugins != null && updatedPlugins.Any()) {
|
||||
if (this.dalamud.Configuration.AutoUpdatePlugins) {
|
||||
if (updatedPlugins != null && updatedPlugins.Any())
|
||||
{
|
||||
if (this.dalamud.Configuration.AutoUpdatePlugins)
|
||||
{
|
||||
this.dalamud.PluginRepository.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
|
||||
} else {
|
||||
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
||||
MessageBytes = new SeString(new List<Payload>() {
|
||||
}
|
||||
else
|
||||
{
|
||||
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(" ["),
|
||||
new UIForegroundPayload(this.dalamud.Data, 500),
|
||||
|
|
@ -239,7 +301,7 @@ namespace Dalamud.Game {
|
|||
new UIForegroundPayload(this.dalamud.Data, 0),
|
||||
new TextPayload("]"),
|
||||
}).Encode(),
|
||||
Type = XivChatType.Urgent
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -248,17 +310,5 @@ namespace Dalamud.Game {
|
|||
|
||||
this.hasSeenLoadingMsg = true;
|
||||
}
|
||||
|
||||
private static SeString MakeItalics(string text) {
|
||||
// TODO: when the above code is switched to SeString, this can be a straight insertion of the
|
||||
// italics payloads only, and be a lot cleaner
|
||||
var italicString = new SeString(new List<Payload>(new Payload[] {
|
||||
EmphasisItalicPayload.ItalicsOn,
|
||||
new TextPayload(text),
|
||||
EmphasisItalicPayload.ItalicsOff
|
||||
}));
|
||||
|
||||
return italicString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,71 +1,55 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
||||
using JetBrains.Annotations;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors {
|
||||
namespace Dalamud.Game.ClientState.Actors
|
||||
{
|
||||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV actors.
|
||||
/// </summary>
|
||||
public class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable {
|
||||
|
||||
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable
|
||||
{
|
||||
private const int ActorTableLength = 424;
|
||||
|
||||
#region Actor Table Cache
|
||||
#region ReadProcessMemory Hack
|
||||
private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
|
||||
private static readonly IntPtr ActorMem = Marshal.AllocHGlobal(ActorMemSize);
|
||||
private static readonly IntPtr CurrentProcessHandle = new(-1);
|
||||
#endregion
|
||||
|
||||
private Dalamud dalamud;
|
||||
private ClientStateAddressResolver address;
|
||||
private List<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>
|
||||
/// Initializes a new instance of the <see cref="ActorTable"/> class.
|
||||
/// Set up the actor table collection.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
|
||||
Address = addressResolver;
|
||||
/// <param name="dalamud">The Dalamud instance.</param>
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
this.dalamud = dalamud;
|
||||
|
||||
dalamud.Framework.OnUpdateEvent += Framework_OnUpdateEvent;
|
||||
dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent;
|
||||
|
||||
Log.Verbose("Actor table address {ActorTable}", Address.ActorTable);
|
||||
Log.Verbose("Actor table address {ActorTable}", this.address.ActorTable);
|
||||
}
|
||||
|
||||
private void Framework_OnUpdateEvent(Internal.Framework framework) {
|
||||
this.ResetCache();
|
||||
}
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
|
@ -73,98 +57,168 @@ namespace Dalamud.Game.ClientState.Actors {
|
|||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
|
||||
[CanBeNull]
|
||||
public Actor this[int index] {
|
||||
get => ActorsCache[index];
|
||||
}
|
||||
public Actor this[int index] => this.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)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
// FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
|
||||
if (!ReadProcessMemory(this.currentProcessHandle, offset, this.actorMem, ActorMemSize, out _))
|
||||
if (!NativeFunctions.ReadProcessMemory(CurrentProcessHandle, offset, ActorMem, ActorMemSize, out _))
|
||||
{
|
||||
Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
|
||||
return null;
|
||||
}
|
||||
|
||||
var actorStruct = Marshal.PtrToStructure<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.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud),
|
||||
ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud),
|
||||
ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud),
|
||||
_ => new Actor(offset, actorStruct, this.dalamud)
|
||||
_ => new Actor(offset, actorStruct, this.dalamud),
|
||||
};
|
||||
}
|
||||
catch (Exception e) {
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not read actor from memory.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr[] GetPointerTable() {
|
||||
private void ResetCache() => this.actorsCache = null;
|
||||
|
||||
private void Framework_OnUpdateEvent(Internal.Framework framework)
|
||||
{
|
||||
this.ResetCache();
|
||||
}
|
||||
|
||||
private IntPtr[] GetPointerTable()
|
||||
{
|
||||
var ret = new IntPtr[ActorTableLength];
|
||||
Marshal.Copy(Address.ActorTable, ret, 0, ActorTableLength);
|
||||
Marshal.Copy(this.address.ActorTable, ret, 0, ActorTableLength);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<Actor> GetActorTable() {
|
||||
private List<Actor> GetActorTable()
|
||||
{
|
||||
var actors = new List<Actor>();
|
||||
var ptrTable = GetPointerTable();
|
||||
for (var i = 0; i < ActorTableLength; i++) {
|
||||
actors.Add(ptrTable[i] != IntPtr.Zero ? ReadActorFromMemory(ptrTable[i]) : null);
|
||||
var ptrTable = this.GetPointerTable();
|
||||
for (var i = 0; i < ActorTableLength; i++)
|
||||
{
|
||||
actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null);
|
||||
}
|
||||
|
||||
return actors;
|
||||
}
|
||||
|
||||
public IEnumerator<Actor> GetEnumerator() {
|
||||
return ActorsCache.Where(a => a != null).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of currently spawned actors.
|
||||
/// Implementing IDisposable.
|
||||
/// </summary>
|
||||
public int Length => ActorsCache.Count;
|
||||
public sealed partial class ActorTable : IDisposable
|
||||
{
|
||||
private bool disposed = false;
|
||||
|
||||
int IReadOnlyCollection<Actor>.Count => Length;
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ActorTable"/> class.
|
||||
/// </summary>
|
||||
~ActorTable() => this.Dispose(false);
|
||||
|
||||
int ICollection.Count => Length;
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/// <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;
|
||||
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
for (var i = 0; i < Length; i++) {
|
||||
/// <summary>
|
||||
/// 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);
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum describes the indices of the Customize array.
|
||||
/// </summary>
|
||||
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
|
||||
public enum CustomizeIndex {
|
||||
public enum CustomizeIndex
|
||||
{
|
||||
/// <summary>
|
||||
/// The race of the character.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
namespace Dalamud.Game.ClientState.Actors {
|
||||
namespace Dalamud.Game.ClientState.Actors
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum describing possible entity kinds.
|
||||
/// </summary>
|
||||
public enum ObjectKind : byte {
|
||||
public enum ObjectKind : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid actor.
|
||||
/// </summary>
|
||||
|
|
@ -57,13 +59,25 @@ namespace Dalamud.Game.ClientState.Actors {
|
|||
/// Objects representing retainers.
|
||||
/// </summary>
|
||||
Retainer = 0x0A,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing area objects.
|
||||
/// </summary>
|
||||
Area = 0x0B,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing housing objects.
|
||||
/// </summary>
|
||||
Housing = 0x0C,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing cutscene objects.
|
||||
/// </summary>
|
||||
Cutscene = 0x0D,
|
||||
CardStand = 0x0E
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing card stand objects.
|
||||
/// </summary>
|
||||
CardStand = 0x0E,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,38 @@
|
|||
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)]
|
||||
public struct Position3 {
|
||||
public struct Position3
|
||||
{
|
||||
/// <summary>
|
||||
/// The X of (X,Z,Y).
|
||||
/// </summary>
|
||||
public float X;
|
||||
|
||||
/// <summary>
|
||||
/// The Z of (X,Z,Y).
|
||||
/// </summary>
|
||||
public float Z;
|
||||
|
||||
/// <summary>
|
||||
/// The Y of (X,Z,Y).
|
||||
/// </summary>
|
||||
public float Y;
|
||||
|
||||
/// <summary>
|
||||
/// Convert this Position3 to a System.Numerics.Vector3
|
||||
/// Convert this Position3 to a System.Numerics.Vector3.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// Convert this Position3 to a SharpDX.Vector3
|
||||
/// Convert this Position3 to a SharpDX.Vector3.
|
||||
/// </summary>
|
||||
/// <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
|
||||
{
|
||||
public abstract class BaseResolver {
|
||||
protected Dalamud dalamud;
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
/// <summary>
|
||||
/// This object represents a class or job.
|
||||
/// </summary>
|
||||
public class ClassJob : BaseResolver {
|
||||
public class ClassJob : BaseResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the ClassJob.
|
||||
/// </summary>
|
||||
public readonly uint Id;
|
||||
|
||||
/// <summary>
|
||||
/// GameData linked to this ClassJob.
|
||||
/// </summary>
|
||||
public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
|
||||
this.dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.ClassJob>().GetRow(this.Id);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClassJob"/> class.
|
||||
/// Set up the ClassJob resolver with the provided ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the world.</param>
|
||||
public ClassJob(byte id, Dalamud dalamud) : base(dalamud) {
|
||||
/// <param name="id">The ID of the classJob.</param>
|
||||
/// <param name="dalamud">The Dalamud instance.</param>
|
||||
public ClassJob(byte id, Dalamud dalamud)
|
||||
: base(dalamud)
|
||||
{
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// This object represents a world a character can reside on.
|
||||
/// </summary>
|
||||
public class World : BaseResolver {
|
||||
public class World : BaseResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the world.
|
||||
/// </summary>
|
||||
public readonly uint Id;
|
||||
|
||||
/// <summary>
|
||||
/// GameData linked to this world.
|
||||
/// </summary>
|
||||
public Lumina.Excel.GeneratedSheets.World GameData =>
|
||||
this.dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.World>().GetRow(this.Id);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="World"/> class.
|
||||
/// Set up the world resolver with the provided ID.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <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.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors {
|
||||
public static class TargetOffsets {
|
||||
public const int CurrentTarget = 0x80;
|
||||
public const int MouseOverTarget = 0xD0;
|
||||
public const int FocusTarget = 0xF8;
|
||||
public const int PreviousTarget = 0x110;
|
||||
public const int SoftTarget = 0x88;
|
||||
}
|
||||
|
||||
public sealed class Targets {
|
||||
private ClientStateAddressResolver Address { get; }
|
||||
namespace Dalamud.Game.ClientState.Actors
|
||||
{
|
||||
/// <summary>
|
||||
/// Get and set various kinds of targets for the player.
|
||||
/// </summary>
|
||||
public sealed class Targets
|
||||
{
|
||||
private Dalamud dalamud;
|
||||
private ClientStateAddressResolver address;
|
||||
|
||||
public Actor CurrentTarget => GetActorByOffset(TargetOffsets.CurrentTarget);
|
||||
public Actor MouseOverTarget => GetActorByOffset(TargetOffsets.MouseOverTarget);
|
||||
public Actor FocusTarget => GetActorByOffset(TargetOffsets.FocusTarget);
|
||||
public Actor PreviousTarget => GetActorByOffset(TargetOffsets.PreviousTarget);
|
||||
public Actor SoftTarget => GetActorByOffset(TargetOffsets.SoftTarget);
|
||||
|
||||
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Targets"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dalamud">The Dalamud instance.</param>
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
Address = addressResolver;
|
||||
this.address = addressResolver;
|
||||
}
|
||||
|
||||
public void SetCurrentTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
|
||||
public void SetCurrentTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.CurrentTarget);
|
||||
/// <summary>
|
||||
/// Gets the current target.
|
||||
/// </summary>
|
||||
public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
|
||||
|
||||
public void SetFocusTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
|
||||
public void SetFocusTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.FocusTarget);
|
||||
/// <summary>
|
||||
/// Gets the mouseover target.
|
||||
/// </summary>
|
||||
public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
|
||||
|
||||
public void ClearCurrentTarget() => SetCurrentTarget(IntPtr.Zero);
|
||||
public void ClearFocusTarget() => SetFocusTarget(IntPtr.Zero);
|
||||
/// <summary>
|
||||
/// Gets the focus target.
|
||||
/// </summary>
|
||||
public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
|
||||
|
||||
private void SetTarget(IntPtr actorAddress, int offset) {
|
||||
if (Address.TargetManager == IntPtr.Zero) return;
|
||||
Marshal.WriteIntPtr(Address.TargetManager, offset, actorAddress);
|
||||
/// <summary>
|
||||
/// Gets the previous target.
|
||||
/// </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) {
|
||||
if (Address.TargetManager == IntPtr.Zero) return null;
|
||||
var actorAddress = Marshal.ReadIntPtr(Address.TargetManager + offset);
|
||||
if (actorAddress == IntPtr.Zero) return null;
|
||||
private Actor GetActorByOffset(int offset)
|
||||
{
|
||||
if (this.address.TargetManager == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
var actorAddress = Marshal.ReadIntPtr(this.address.TargetManager + offset);
|
||||
if (actorAddress == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ namespace Dalamud.Game.ClientState.Actors.Types
|
|||
public class Actor : IEquatable<Actor>
|
||||
{
|
||||
private readonly Structs.Actor actorStruct;
|
||||
// This is a breaking change. StyleCop demands it.
|
||||
// private readonly IntPtr address;
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -83,8 +81,6 @@ namespace Dalamud.Game.ClientState.Actors.Types
|
|||
/// <summary>
|
||||
/// Gets the address of this actor in memory.
|
||||
/// </summary>
|
||||
// TODO: This is a breaking change, StyleCop demands it.
|
||||
// public IntPtr Address => this.address;
|
||||
public readonly IntPtr Address;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace Dalamud.Game.ClientState.Actors.Types
|
|||
/// <summary>
|
||||
/// Gets the ClassJob of this Chara.
|
||||
/// </summary>
|
||||
public ClassJob ClassJob => new ClassJob(this.ActorStruct.ClassJob, this.Dalamud);
|
||||
public ClassJob ClassJob => new(this.ActorStruct.ClassJob, this.Dalamud);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current HP of this Chara.
|
||||
|
|
|
|||
|
|
@ -2,27 +2,51 @@ using System.Runtime.InteropServices;
|
|||
|
||||
namespace Dalamud.Game.ClientState.Actors.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a party member.
|
||||
/// </summary>
|
||||
public class PartyMember
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the character.
|
||||
/// </summary>
|
||||
public string CharacterName;
|
||||
|
||||
/// <summary>
|
||||
/// Unknown.
|
||||
/// </summary>
|
||||
public long Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// The actor object that corresponds to this party member.
|
||||
/// </summary>
|
||||
public Actor Actor;
|
||||
|
||||
/// <summary>
|
||||
/// The kind or type of actor.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
|
||||
Unknown = rawData.unknown;
|
||||
Actor = null;
|
||||
this.CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
|
||||
this.Unknown = rawData.unknown;
|
||||
this.Actor = null;
|
||||
for (var i = 0; i < table.Length; i++)
|
||||
{
|
||||
if (table[i] != null && table[i].ActorId == rawData.actorId)
|
||||
{
|
||||
Actor = table[i];
|
||||
this.Actor = table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
ObjectKind = rawData.objectKind;
|
||||
|
||||
this.ObjectKind = rawData.objectKind;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ namespace Dalamud.Game.ClientState.Actors.Types
|
|||
/// <summary>
|
||||
/// Gets the current <see cref="World">world</see> of the character.
|
||||
/// </summary>
|
||||
public World CurrentWorld => new World(this.ActorStruct.CurrentWorld, this.Dalamud);
|
||||
public World CurrentWorld => new(this.ActorStruct.CurrentWorld, this.Dalamud);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home <see cref="World">world</see> of the character.
|
||||
/// </summary>
|
||||
public World HomeWorld => new World(this.ActorStruct.HomeWorld, this.Dalamud);
|
||||
public World HomeWorld => new(this.ActorStruct.HomeWorld, this.Dalamud);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Free Company tag of this player.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.ClientState.Actors;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.Internal;
|
||||
using Dalamud.Game.Internal.Network;
|
||||
using Dalamud.Hooking;
|
||||
using JetBrains.Annotations;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
|
@ -15,41 +15,17 @@ namespace Dalamud.Game.ClientState
|
|||
/// <summary>
|
||||
/// This class represents the state of the game client at the time of access.
|
||||
/// </summary>
|
||||
public class ClientState : INotifyPropertyChanged, IDisposable {
|
||||
private readonly Dalamud dalamud;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private ClientStateAddressResolver Address { get; }
|
||||
|
||||
public readonly ClientLanguage ClientLanguage;
|
||||
|
||||
public class ClientState : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The table of all present actors.
|
||||
/// </summary>
|
||||
public readonly ActorTable Actors;
|
||||
|
||||
/// <summary>
|
||||
/// The local player character, if one is present.
|
||||
/// Gets the language of the client.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
public PlayerCharacter LocalPlayer {
|
||||
get {
|
||||
var actor = this.Actors[0];
|
||||
|
||||
if (actor is PlayerCharacter pc)
|
||||
return pc;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#region TerritoryType
|
||||
|
||||
// TODO: The hooking logic for this should go into a separate class.
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
||||
|
||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||
public readonly ClientLanguage ClientLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// The current Territory the player resides in.
|
||||
|
|
@ -57,39 +33,12 @@ namespace Dalamud.Game.ClientState
|
|||
public ushort TerritoryType;
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when the current Territory changes.
|
||||
/// </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
|
||||
/// The class facilitating Job Gauge data access.
|
||||
/// </summary>
|
||||
public JobGauges JobGauges;
|
||||
|
||||
/// <summary>
|
||||
/// The class facilitating party list data access
|
||||
/// The class facilitating party list data access.
|
||||
/// </summary>
|
||||
public PartyList PartyList;
|
||||
|
||||
|
|
@ -109,70 +58,69 @@ namespace Dalamud.Game.ClientState
|
|||
public Condition Condition;
|
||||
|
||||
/// <summary>
|
||||
/// The class facilitating target data access
|
||||
/// The class facilitating target data access.
|
||||
/// </summary>
|
||||
public Targets Targets;
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="dalamud">Dalamud instance</param>
|
||||
/// /// <param name="startInfo">StartInfo of the current Dalamud launch</param>
|
||||
/// <param name="scanner">Sig scanner</param>
|
||||
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner) {
|
||||
/// <param name="dalamud">Dalamud instance.</param>
|
||||
/// <param name="startInfo">StartInfo of the current Dalamud launch.</param>
|
||||
/// <param name="scanner">Sig scanner.</param>
|
||||
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
Address = new ClientStateAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
this.address = new ClientStateAddressResolver();
|
||||
this.address.Setup(scanner);
|
||||
|
||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
||||
|
||||
this.ClientLanguage = startInfo.Language;
|
||||
|
||||
this.Actors = new ActorTable(dalamud, Address);
|
||||
this.Actors = new ActorTable(dalamud, this.address);
|
||||
|
||||
this.PartyList = new PartyList(dalamud, Address);
|
||||
this.PartyList = new PartyList(dalamud, this.address);
|
||||
|
||||
this.JobGauges = new JobGauges(Address);
|
||||
this.JobGauges = new JobGauges(this.address);
|
||||
|
||||
this.KeyState = new KeyState(Address, scanner.Module.BaseAddress);
|
||||
this.KeyState = new KeyState(this.address, scanner.Module.BaseAddress);
|
||||
|
||||
this.GamepadState = new GamepadState(this.Address);
|
||||
this.GamepadState = new GamepadState(this.address);
|
||||
|
||||
this.Condition = new Condition( Address );
|
||||
this.Condition = new Condition(this.address);
|
||||
|
||||
this.Targets = new Targets(dalamud, Address);
|
||||
this.Targets = new Targets(dalamud, this.address);
|
||||
|
||||
Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", Address.SetupTerritoryType);
|
||||
Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", this.address.SetupTerritoryType);
|
||||
|
||||
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(Address.SetupTerritoryType,
|
||||
new SetupTerritoryTypeDelegate(SetupTerritoryTypeDetour),
|
||||
this);
|
||||
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, new SetupTerritoryTypeDelegate(this.SetupTerritoryTypeDetour), this);
|
||||
|
||||
dalamud.Framework.OnUpdateEvent += FrameworkOnOnUpdateEvent;
|
||||
dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
|
||||
dalamud.Framework.OnUpdateEvent += this.FrameworkOnOnUpdateEvent;
|
||||
dalamud.NetworkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||
}
|
||||
|
||||
private void NetworkHandlersOnCfPop(object sender, ContentFinderCondition e) {
|
||||
CfPop?.Invoke(this, e);
|
||||
}
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
||||
|
||||
public void Enable() {
|
||||
this.GamepadState.Enable();
|
||||
this.PartyList.Enable();
|
||||
this.setupTerritoryTypeHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.PartyList.Dispose();
|
||||
this.setupTerritoryTypeHook.Dispose();
|
||||
this.Actors.Dispose();
|
||||
this.GamepadState.Dispose();
|
||||
|
||||
this.dalamud.Framework.OnUpdateEvent -= FrameworkOnOnUpdateEvent;
|
||||
this.dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
|
||||
}
|
||||
|
||||
private bool lastConditionNone = true;
|
||||
/// <summary>
|
||||
/// Event that fires when a property changes.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0067
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
#pragma warning restore
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a character is logging in.
|
||||
|
|
@ -184,24 +132,93 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
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>
|
||||
/// Gets a value indicating whether a character is logged in.
|
||||
/// </summary>
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
|
||||
private void FrameworkOnOnUpdateEvent(Framework framework) {
|
||||
if (this.Condition.Any() && this.lastConditionNone == true) {
|
||||
/// <summary>
|
||||
/// 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");
|
||||
this.lastConditionNone = false;
|
||||
this.IsLoggedIn = true;
|
||||
OnLogin?.Invoke(this, null);
|
||||
this.OnLogin?.Invoke(this, null);
|
||||
}
|
||||
|
||||
if (!this.Condition.Any() && this.lastConditionNone == false) {
|
||||
if (!this.Condition.Any() && this.lastConditionNone == false)
|
||||
{
|
||||
Log.Debug("Is logout");
|
||||
this.lastConditionNone = true;
|
||||
this.IsLoggedIn = false;
|
||||
OnLogout?.Invoke(this, null);
|
||||
this.OnLogout?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,88 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.Internal;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
public sealed class ClientStateAddressResolver : BaseAddressResolver {
|
||||
/// <summary>
|
||||
/// Client state memory address resolver.
|
||||
/// </summary>
|
||||
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||
{
|
||||
// Static offsets
|
||||
public IntPtr ActorTable { get; private set; }
|
||||
//public IntPtr ViewportActorTable { get; private set; }
|
||||
public IntPtr LocalContentId { get; private set; }
|
||||
public IntPtr JobGaugeData { get; private set; }
|
||||
public IntPtr KeyboardState { get; private set; }
|
||||
public IntPtr TargetManager { get; private set; }
|
||||
|
||||
// Functions
|
||||
public IntPtr SetupTerritoryType { get; private set; }
|
||||
//public IntPtr SomeActorTableAccess { get; private set; }
|
||||
//public IntPtr PartyListUpdate { get; private set; }
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public IntPtr GamepadPoll { get; private set; }
|
||||
|
||||
public IntPtr ConditionFlags { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
// We don't need those anymore, but maybe someone else will - let's leave them here for good measure
|
||||
//ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
|
||||
//SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
|
||||
ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
|
||||
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
|
||||
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
|
||||
this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
|
||||
|
||||
LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
||||
JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
|
||||
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
||||
this.JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
|
||||
|
||||
SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
|
||||
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
|
||||
|
||||
// This resolves to a fixed offset only, without the base address added in, so GetStaticAddressFromSig() can't be used
|
||||
KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
||||
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
||||
|
||||
//PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
|
||||
// PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
|
||||
|
||||
ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
|
||||
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
|
||||
|
||||
TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
|
||||
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
|
||||
|
||||
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
|
|
@ -10,36 +7,49 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
public class Condition
|
||||
{
|
||||
internal readonly IntPtr conditionArrayBase;
|
||||
|
||||
/// <summary>
|
||||
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||
/// </summary>
|
||||
public const int MaxConditionEntries = 100;
|
||||
|
||||
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>
|
||||
/// Check the value of a specific condition/state flag.
|
||||
/// </summary>
|
||||
/// <param name="flag">The condition flag to check</param>
|
||||
public unsafe bool this[ ConditionFlag flag ]
|
||||
/// <param name="flag">The condition flag to check.</param>
|
||||
public unsafe bool this[ConditionFlag flag]
|
||||
{
|
||||
get
|
||||
{
|
||||
var idx = ( int )flag;
|
||||
var idx = (int)flag;
|
||||
|
||||
if( idx > MaxConditionEntries || idx < 0 )
|
||||
if (idx > MaxConditionEntries || idx < 0)
|
||||
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++)
|
||||
{
|
||||
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
|
||||
/// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to.
|
||||
/// </summary>
|
||||
public enum ConditionFlag {
|
||||
public enum ConditionFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// Unused.
|
||||
/// </summary>
|
||||
|
|
@ -27,9 +28,6 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
Emoting = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounted.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Unable to execute command while mounted.
|
||||
/// </summary>
|
||||
|
|
@ -95,14 +93,14 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
Performing = 16,
|
||||
|
||||
//Unknown17 = 17,
|
||||
//Unknown18 = 18,
|
||||
//Unknown19 = 19,
|
||||
//Unknown20 = 20,
|
||||
//Unknown21 = 21,
|
||||
//Unknown22 = 22,
|
||||
//Unknown23 = 23,
|
||||
//Unknown24 = 24,
|
||||
// Unknown17 = 17,
|
||||
// Unknown18 = 18,
|
||||
// Unknown19 = 19,
|
||||
// Unknown20 = 20,
|
||||
// Unknown21 = 21,
|
||||
// Unknown22 = 22,
|
||||
// Unknown23 = 23,
|
||||
// Unknown24 = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while occupied.
|
||||
|
|
@ -200,7 +198,7 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
Fishing = 43,
|
||||
|
||||
//Unknown44 = 44,
|
||||
// Unknown44 = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while between areas.
|
||||
|
|
@ -212,7 +210,7 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
Stealthed = 46,
|
||||
|
||||
//Unknown47 = 47,
|
||||
// Unknown47 = 47,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while jumping.
|
||||
|
|
@ -400,7 +398,7 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
||||
|
||||
//Unknown85 = 85,
|
||||
// Unknown85 = 85,
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
///
|
||||
/// Ideally, we would use
|
||||
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
|
||||
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
|
||||
/// but this has a race condition during load with the detour which sets up ImGui
|
||||
/// and throws if our detour gets called before the other.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
using Serilog;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper around the game keystate buffer, which contains the pressed state for
|
||||
/// all keyboard keys, indexed by virtual vkCode
|
||||
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
|
||||
/// </summary>
|
||||
public class KeyState
|
||||
{
|
||||
private IntPtr bufferBase;
|
||||
|
||||
// The array is accessed in a way that this limit doesn't appear to exist
|
||||
// but there is other state data past this point, and keys beyond here aren't
|
||||
// generally valid for most things anyway
|
||||
private const int MaxKeyCodeIndex = 0xA0;
|
||||
private IntPtr bufferBase;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
||||
|
|
@ -33,10 +37,10 @@ namespace Dalamud.Game.ClientState
|
|||
{
|
||||
get
|
||||
{
|
||||
if (vkCode< 0 || vkCode > MaxKeyCodeIndex)
|
||||
if (vkCode < 0 || vkCode > MaxKeyCodeIndex)
|
||||
throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}");
|
||||
|
||||
return (Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0);
|
||||
return Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0;
|
||||
}
|
||||
|
||||
set
|
||||
|
|
|
|||
|
|
@ -1,96 +1,139 @@
|
|||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
// using Dalamud.Hooking;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
public class PartyList : IReadOnlyCollection<PartyMember>, ICollection, IDisposable
|
||||
/// <summary>
|
||||
/// This class represents the members of your party.
|
||||
/// </summary>
|
||||
public sealed partial class PartyList
|
||||
{
|
||||
private ClientStateAddressResolver Address { get; }
|
||||
private Dalamud dalamud;
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
// private bool isReady = false;
|
||||
// private IntPtr partyListBegin;
|
||||
// private Hook<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 Hook<PartyListUpdateDelegate> partyListUpdateHook;
|
||||
private IntPtr partyListBegin;
|
||||
private bool isReady = false;
|
||||
/// <summary>
|
||||
/// Gets the length of the PartyList.
|
||||
/// </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;
|
||||
this.dalamud = dalamud;
|
||||
//this.partyListUpdateHook = new Hook<PartyListUpdateDelegate>(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
|
||||
get
|
||||
{
|
||||
return null;
|
||||
// if (!this.isReady)
|
||||
// return null;
|
||||
// if (index >= this.Length)
|
||||
// return null;
|
||||
// var tblIndex = this.partyListBegin + (index * 24);
|
||||
// var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
|
||||
// return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
// TODO Fix for 5.3
|
||||
//this.partyListUpdateHook.Enable();
|
||||
// this.partyListUpdateHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
//if (!this.isReady)
|
||||
// if (!this.isReady)
|
||||
// this.partyListUpdateHook.Dispose();
|
||||
this.isReady = false;
|
||||
// this.isReady = false;
|
||||
}
|
||||
|
||||
private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
|
||||
{
|
||||
var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
|
||||
this.partyListBegin = structBegin + 0xB48;
|
||||
this.partyListUpdateHook.Dispose();
|
||||
this.isReady = true;
|
||||
return result;
|
||||
// private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
|
||||
// {
|
||||
// var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
|
||||
// this.partyListBegin = structBegin + 0xB48;
|
||||
// this.partyListUpdateHook.Dispose();
|
||||
// this.isReady = true;
|
||||
// return result;
|
||||
// }
|
||||
}
|
||||
|
||||
public PartyMember this[int index]
|
||||
/// <summary>
|
||||
/// Implements IReadOnlyCollection, IEnumerable.
|
||||
/// </summary>
|
||||
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
|
||||
{
|
||||
get {
|
||||
if (!this.isReady)
|
||||
return null;
|
||||
if (index >= Length)
|
||||
return null;
|
||||
var tblIndex = partyListBegin + index * 24;
|
||||
var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
|
||||
return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
||||
|
||||
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++)
|
||||
{
|
||||
if (this[i] != null)
|
||||
{
|
||||
array.SetValue(this[i], index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<PartyMember> GetEnumerator() {
|
||||
for (var i = 0; i < Length; i++) {
|
||||
if (this[i] != null) {
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
|
||||
public int Length => !this.isReady ? 0 : Marshal.ReadByte(partyListBegin + 0xF0);
|
||||
|
||||
int IReadOnlyCollection<PartyMember>.Count => Length;
|
||||
|
||||
public int Count => Length;
|
||||
/// <summary>
|
||||
/// Implements ICollection.
|
||||
/// </summary>
|
||||
public sealed partial class PartyList : ICollection
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object SyncRoot => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
{
|
||||
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
|
||||
public const int Name = 48; // 0x0030
|
||||
|
|
@ -48,51 +286,4 @@ namespace Dalamud.Game.ClientState.Structs
|
|||
public const int TotalCastTime = 0x1BB8;
|
||||
public const int UIStatusEffects = 0x19F8;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs.JobGauge
|
||||
{
|
||||
#region AST
|
||||
|
|
@ -270,3 +272,5 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
|
|||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
|
|
|
|||
|
|
@ -1,19 +1,26 @@
|
|||
using Dalamud.Game.ClientState.Actors;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.ClientState.Actors;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents a native PartyMember class in memory.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct PartyMember
|
||||
{
|
||||
[FieldOffset(0x0)] public IntPtr namePtr;
|
||||
[FieldOffset(0x8)] public long unknown;
|
||||
[FieldOffset(0x10)] public int actorId;
|
||||
[FieldOffset(0x14)] public ObjectKind objectKind;
|
||||
[FieldOffset(0x0)]
|
||||
public IntPtr namePtr;
|
||||
|
||||
[FieldOffset(0x8)]
|
||||
public long unknown;
|
||||
|
||||
[FieldOffset(0x10)]
|
||||
public int actorId;
|
||||
|
||||
[FieldOffset(0x14)]
|
||||
public ObjectKind objectKind;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs
|
||||
|
|
@ -9,10 +8,29 @@ namespace Dalamud.Game.ClientState.Structs
|
|||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The effect ID.
|
||||
/// </summary>
|
||||
public short EffectId;
|
||||
|
||||
/// <summary>
|
||||
/// How many stacks are present.
|
||||
/// </summary>
|
||||
public byte StackCount;
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters.
|
||||
/// </summary>
|
||||
public byte Param;
|
||||
|
||||
/// <summary>
|
||||
/// The duration remaining.
|
||||
/// </summary>
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the actor that caused this effect.
|
||||
/// </summary>
|
||||
public int OwnerId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Dalamud.Game.Command {
|
||||
namespace Dalamud.Game.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// This class describes a registered command.
|
||||
/// </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>
|
||||
/// The function to be executed when the command is dispatched.
|
||||
/// </summary>
|
||||
|
|
@ -13,29 +26,23 @@ namespace Dalamud.Game.Command {
|
|||
public delegate void HandlerDelegate(string command, string arguments);
|
||||
|
||||
/// <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>
|
||||
public HandlerDelegate Handler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The help message for this command.
|
||||
/// Gets or sets the help message for this command.
|
||||
/// </summary>
|
||||
public string HelpMessage { get; set; } = string.Empty;
|
||||
|
||||
/// <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>
|
||||
public bool ShowInHelp { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new CommandInfo with the provided handler.
|
||||
/// Gets or sets the name of the assembly responsible for this command.
|
||||
/// </summary>
|
||||
/// <param name="handler"></param>
|
||||
public CommandInfo(HandlerDelegate handler) {
|
||||
Handler = handler;
|
||||
LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
||||
}
|
||||
|
||||
internal string LoaderAssemblyName { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,45 +2,37 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Command {
|
||||
namespace Dalamud.Game.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// This class manages registered in-game slash commands.
|
||||
/// </summary>
|
||||
public sealed class CommandManager {
|
||||
public sealed class CommandManager
|
||||
{
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private readonly Dictionary<string, CommandInfo> commandMap = new Dictionary<string, CommandInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Read-only list of all registered commands.
|
||||
/// </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 Dictionary<string, CommandInfo> commandMap = new();
|
||||
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexFr = new(@"^La commande texte “(?<command>.+)” n'existe pas\.$", RegexOptions.Compiled);
|
||||
private readonly Regex currentLangCommandRegex;
|
||||
|
||||
|
||||
public CommandManager(Dalamud dalamud, ClientLanguage language) {
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
switch (language) {
|
||||
switch (language)
|
||||
{
|
||||
case ClientLanguage.Japanese:
|
||||
this.currentLangCommandRegex = this.commandRegexJp;
|
||||
break;
|
||||
|
|
@ -55,40 +47,42 @@ namespace Dalamud.Game.Command {
|
|||
break;
|
||||
}
|
||||
|
||||
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
|
||||
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += this.OnCheckMessageHandled;
|
||||
}
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
|
||||
if (type == XivChatType.ErrorMessage && senderId == 0) {
|
||||
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
||||
if (cmdMatch.Success) {
|
||||
// Yes, it's a chat command.
|
||||
var command = cmdMatch.Value;
|
||||
if (ProcessCommand(command)) isHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all registered commands.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, CommandInfo> Commands => new(this.commandMap);
|
||||
|
||||
/// <summary>
|
||||
/// Process a command in full.
|
||||
/// </summary>
|
||||
/// <param name="content">The full command string.</param>
|
||||
/// <returns>True if the command was found and dispatched.</returns>
|
||||
public bool ProcessCommand(string content) {
|
||||
public bool ProcessCommand(string content)
|
||||
{
|
||||
string command;
|
||||
string argument;
|
||||
|
||||
var separatorPosition = content.IndexOf(' ');
|
||||
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length) {
|
||||
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length)
|
||||
{
|
||||
// If no space was found or ends with the space. Process them as a no argument
|
||||
if (separatorPosition + 1 >= content.Length) {
|
||||
if (separatorPosition + 1 >= content.Length)
|
||||
{
|
||||
// Remove the trailing space
|
||||
command = content.Substring(0, separatorPosition);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
command = content;
|
||||
}
|
||||
|
||||
argument = string.Empty;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// e.g.)
|
||||
// /testcommand arg1
|
||||
// => Total of 17 chars
|
||||
|
|
@ -98,13 +92,13 @@ namespace Dalamud.Game.Command {
|
|||
command = content.Substring(0, separatorPosition);
|
||||
|
||||
var argStart = separatorPosition + 1;
|
||||
argument = content.Substring(argStart, content.Length - argStart);
|
||||
argument = content[argStart..];
|
||||
}
|
||||
|
||||
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
||||
return false;
|
||||
|
||||
DispatchCommand(command, argument, handler);
|
||||
this.DispatchCommand(command, argument, handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -114,12 +108,15 @@ namespace Dalamud.Game.Command {
|
|||
/// <param name="command">The command to dispatch.</param>
|
||||
/// <param name="argument">The provided arguments.</param>
|
||||
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
||||
public void DispatchCommand(string command, string argument, CommandInfo info) {
|
||||
try {
|
||||
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
info.Handler(command, argument);
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command,
|
||||
argument);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,13 +126,17 @@ namespace Dalamud.Game.Command {
|
|||
/// <param name="command">The command to register.</param>
|
||||
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
||||
/// <returns>If adding was successful.</returns>
|
||||
public bool AddHandler(string command, CommandInfo info) {
|
||||
public bool AddHandler(string command, CommandInfo info)
|
||||
{
|
||||
if (info == null) throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
this.commandMap.Add(command, info);
|
||||
return true;
|
||||
} catch (ArgumentException) {
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Log.Error("Command {CommandName} is already registered.", command);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -146,8 +147,23 @@ namespace Dalamud.Game.Command {
|
|||
/// </summary>
|
||||
/// <param name="command">The command to remove.</param>
|
||||
/// <returns>If the removal was successful.</returns>
|
||||
public bool RemoveHandler(string command) {
|
||||
public bool RemoveHandler(string command)
|
||||
{
|
||||
return this.commandMap.Remove(command);
|
||||
}
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
if (type == XivChatType.ErrorMessage && senderId == 0)
|
||||
{
|
||||
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
||||
if (cmdMatch.Success)
|
||||
{
|
||||
// Yes, it's a chat command.
|
||||
var command = cmdMatch.Value;
|
||||
if (this.ProcessCommand(command)) isHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using EasyHook;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal
|
||||
{
|
||||
public class AntiDebug : IDisposable
|
||||
/// <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 byte[] original;
|
||||
private IntPtr debugCheckAddress;
|
||||
|
||||
public void Enable() {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AntiDebug"/> class.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
public AntiDebug(SigScanner scanner)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
this.debugCheckAddress = IntPtr.Zero;
|
||||
}
|
||||
|
||||
Log.Verbose("DebugCheck address {DebugCheckAddress}", this.debugCheckAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the anti-debugging is enabled.
|
||||
/// </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 (DebugCheckAddress != IntPtr.Zero && !IsEnabled) {
|
||||
Log.Information($"Overwriting Debug Check @ 0x{DebugCheckAddress.ToInt64():X}");
|
||||
SafeMemory.ReadBytes(DebugCheckAddress, this.nop.Length, out this.original);
|
||||
SafeMemory.WriteBytes(DebugCheckAddress, this.nop);
|
||||
} else {
|
||||
Log.Information("DebugCheck already overwritten?");
|
||||
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?");
|
||||
}
|
||||
|
||||
IsEnabled = true;
|
||||
this.IsEnabled = true;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
//if (this.DebugCheckAddress != IntPtr.Zero && this.original != null)
|
||||
// Marshal.Copy(this.original, 0, DebugCheckAddress, this.nop.Length);
|
||||
/// <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,50 +3,69 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Internal {
|
||||
public abstract class BaseAddressResolver {
|
||||
namespace Dalamud.Game.Internal
|
||||
{
|
||||
/// <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; }
|
||||
|
||||
public static Dictionary<string, List<(string, IntPtr)>> DebugScannedValues = new Dictionary<string, List<(string, IntPtr)>>();
|
||||
|
||||
public void Setup(SigScanner scanner) {
|
||||
/// <summary>
|
||||
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
public void Setup(SigScanner scanner)
|
||||
{
|
||||
// Because C# don't allow to call virtual function while in ctor
|
||||
// we have to do this shit :\
|
||||
|
||||
if (IsResolved) {
|
||||
if (this.IsResolved)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.Is32BitProcess) {
|
||||
Setup32Bit(scanner);
|
||||
} else {
|
||||
Setup64Bit(scanner);
|
||||
if (scanner.Is32BitProcess)
|
||||
{
|
||||
this.Setup32Bit(scanner);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Setup64Bit(scanner);
|
||||
}
|
||||
SetupInternal(scanner);
|
||||
|
||||
var className = GetType().Name;
|
||||
this.SetupInternal(scanner);
|
||||
|
||||
var className = this.GetType().Name;
|
||||
DebugScannedValues[className] = new List<(string, IntPtr)>();
|
||||
|
||||
foreach (var property in GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) {
|
||||
DebugScannedValues[className].Add((property.Name, (IntPtr) property.GetValue(this)));
|
||||
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
||||
{
|
||||
DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this)));
|
||||
}
|
||||
|
||||
IsResolved = true;
|
||||
this.IsResolved = true;
|
||||
}
|
||||
|
||||
protected virtual void Setup32Bit(SigScanner scanner) {
|
||||
throw new NotSupportedException("32 bit version is not supported.");
|
||||
}
|
||||
|
||||
protected virtual void Setup64Bit(SigScanner sig) {
|
||||
throw new NotSupportedException("64 bit version is not supported.");
|
||||
}
|
||||
|
||||
protected virtual void SetupInternal(SigScanner scanner) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class {
|
||||
/// <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);
|
||||
|
||||
|
|
@ -55,5 +74,32 @@ namespace Dalamud.Game.Internal {
|
|||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void Setup32Bit(SigScanner scanner)
|
||||
{
|
||||
throw new NotSupportedException("32 bit version is not supported.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
throw new NotSupportedException("64 bit version is not supported.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the resolver by finding any necessary memory addresses.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
protected virtual void SetupInternal(SigScanner scanner)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Internal.DXGI {
|
||||
public interface ISwapChainAddressResolver {
|
||||
namespace Dalamud.Game.Internal.DXGI
|
||||
{
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the address of the native D3D11.ResizeBuffers method.
|
||||
/// </summary>
|
||||
IntPtr ResizeBuffers { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.DXGI
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI.
|
||||
/// </summary>
|
||||
public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Present { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr ResizeBuffers { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
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);
|
||||
|
||||
// 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.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpDX.Windows;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using SharpDX.Direct3D;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.DXGI;
|
||||
|
||||
using Device = SharpDX.Direct3D11.Device;
|
||||
|
||||
namespace Dalamud.Game.Internal.DXGI
|
||||
{
|
||||
/*
|
||||
* This method of getting the SwapChain Addresses is currently not used.
|
||||
* If the normal AddressResolver(SigScanner) fails, we should use it as a fallback.(Linux?)
|
||||
*/
|
||||
/// <summary>
|
||||
/// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the normal signature based method of resolution fails, this is the backup.
|
||||
/// </remarks>
|
||||
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||
{
|
||||
private const int DxgiSwapchainMethodCount = 18;
|
||||
private const int D3D11DeviceMethodCount = 43;
|
||||
|
||||
private static SwapChainDescription CreateSwapChainDescription(IntPtr renderForm) {
|
||||
return new SwapChainDescription {
|
||||
private List<IntPtr> d3d11VTblAddresses;
|
||||
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,
|
||||
Flags = SwapChainFlags.None,
|
||||
IsWindowed = true,
|
||||
|
|
@ -27,74 +71,23 @@ namespace Dalamud.Game.Internal.DXGI
|
|||
OutputHandle = renderForm,
|
||||
SampleDescription = new SampleDescription(1, 0),
|
||||
SwapEffect = SwapEffect.Discard,
|
||||
Usage = Usage.RenderTargetOutput
|
||||
Usage = Usage.RenderTargetOutput,
|
||||
};
|
||||
}
|
||||
|
||||
private IntPtr[] GetVTblAddresses(IntPtr pointer, int numberOfMethods)
|
||||
private List<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>();
|
||||
IntPtr vTable = Marshal.ReadIntPtr(pointer);
|
||||
for (int i = startIndex; i < startIndex + numberOfMethods; i++)
|
||||
var vtblAddresses = new List<IntPtr>();
|
||||
var vTable = Marshal.ReadIntPtr(pointer);
|
||||
for (var i = startIndex; i < startIndex + numberOfMethods; i++)
|
||||
vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes
|
||||
|
||||
return vtblAddresses.ToArray();
|
||||
}
|
||||
|
||||
private List<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];
|
||||
return vtblAddresses;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,95 +3,154 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game.Internal.Gui;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Dalamud.Game.Internal.Network;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal {
|
||||
namespace Dalamud.Game.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
||||
/// </summary>
|
||||
public sealed class Framework : IDisposable {
|
||||
private readonly Dalamud dalamud;
|
||||
public sealed class Framework : IDisposable
|
||||
{
|
||||
private static Stopwatch statsStopwatch = new();
|
||||
|
||||
internal bool DispatchUpdateEvents { get; set; } = true;
|
||||
private readonly Dalamud dalamud;
|
||||
private Hook<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)]
|
||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||
|
||||
private delegate IntPtr OnDestroyDetour();
|
||||
|
||||
public delegate void OnUpdateDelegate(Framework framework);
|
||||
|
||||
public delegate IntPtr OnDestroyDelegate();
|
||||
|
||||
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
||||
private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired every time the game framework updates.
|
||||
/// </summary>
|
||||
public event OnUpdateDelegate OnUpdateEvent;
|
||||
|
||||
private Hook<OnUpdateDetour> updateHook;
|
||||
|
||||
private Hook<OnDestroyDetour> destroyHook;
|
||||
|
||||
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
||||
|
||||
/// <summary>
|
||||
/// A raw pointer to the instance of Client::Framework
|
||||
/// Gets or sets a value indicating whether the collection of stats is enabled.
|
||||
/// </summary>
|
||||
public FrameworkAddressResolver Address { get; }
|
||||
|
||||
#region Stats
|
||||
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>
|
||||
/// 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>
|
||||
public GameGui Gui { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Network subsystem, used to access network data.
|
||||
/// Gets the Network subsystem, used to access network data.
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
#endregion
|
||||
|
||||
public Framework(SigScanner scanner, Dalamud dalamud) {
|
||||
this.dalamud = dalamud;
|
||||
Address = new FrameworkAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
/// <summary>
|
||||
/// Gets a raw pointer to the instance of Client::Framework.
|
||||
/// </summary>
|
||||
public FrameworkAddressResolver Address { get; }
|
||||
|
||||
Log.Verbose("Framework address {FrameworkAddress}", Address.BaseAddress);
|
||||
if (Address.BaseAddress == IntPtr.Zero) {
|
||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
||||
/// <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();
|
||||
}
|
||||
|
||||
// Hook virtual functions
|
||||
HookVTable();
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Gui.Dispose();
|
||||
this.Network.Dispose();
|
||||
|
||||
// Initialize subsystems
|
||||
Libc = new LibcFunction(scanner);
|
||||
|
||||
Gui = new GameGui(Address.GuiManager, scanner, dalamud);
|
||||
|
||||
Network = new GameNetwork(scanner);
|
||||
this.updateHook.Dispose();
|
||||
this.destroyHook.Dispose();
|
||||
this.realDestroyHook.Dispose();
|
||||
}
|
||||
|
||||
private void HookVTable() {
|
||||
var vtable = Marshal.ReadIntPtr(Address.BaseAddress);
|
||||
private void HookVTable()
|
||||
{
|
||||
var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress);
|
||||
// Virtual function layout:
|
||||
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
||||
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
||||
|
|
@ -100,36 +159,17 @@ namespace Dalamud.Game.Internal {
|
|||
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
||||
|
||||
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
||||
this.updateHook = new Hook<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);
|
||||
this.destroyHook =
|
||||
new Hook<OnDestroyDetour>(pDestroy, new OnDestroyDelegate(HandleFrameworkDestroy), this);
|
||||
this.destroyHook = new Hook<OnDestroyDetour>(pDestroy, new OnDestroyDelegate(this.HandleFrameworkDestroy), this);
|
||||
|
||||
var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2);
|
||||
this.realDestroyHook =
|
||||
new Hook<OnRealDestroyDelegate>(pRealDestroy, new OnRealDestroyDelegate(HandleRealDestroy), this);
|
||||
this.realDestroyHook = new Hook<OnRealDestroyDelegate>(pRealDestroy, new OnRealDestroyDelegate(this.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.dalamud.IsReady)
|
||||
this.dalamud.LoadTier2();
|
||||
|
|
@ -137,47 +177,62 @@ namespace Dalamud.Game.Internal {
|
|||
if (!this.dalamud.IsLoadedPluginSystem && this.dalamud.InterfaceManager.IsReady)
|
||||
this.dalamud.LoadTier3();
|
||||
|
||||
try {
|
||||
Gui.Chat.UpdateQueue(this);
|
||||
Gui.Toast.UpdateQueue();
|
||||
Network.UpdateQueue(this);
|
||||
} catch (Exception ex) {
|
||||
try
|
||||
{
|
||||
this.Gui.Chat.UpdateQueue(this);
|
||||
this.Gui.Toast.UpdateQueue();
|
||||
this.Network.UpdateQueue(this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
||||
}
|
||||
|
||||
if (this.DispatchUpdateEvents)
|
||||
{
|
||||
try {
|
||||
if (StatsEnabled && OnUpdateEvent != null) {
|
||||
try
|
||||
{
|
||||
if (StatsEnabled && this.OnUpdateEvent != null)
|
||||
{
|
||||
// Stat Tracking for Framework Updates
|
||||
var invokeList = OnUpdateEvent.GetInvocationList();
|
||||
var invokeList = this.OnUpdateEvent.GetInvocationList();
|
||||
var notUpdated = StatsHistory.Keys.ToList();
|
||||
// Individually invoke OnUpdate handlers and time them.
|
||||
foreach (var d in invokeList) {
|
||||
foreach (var d in invokeList)
|
||||
{
|
||||
statsStopwatch.Restart();
|
||||
d.Method.Invoke(d.Target, new object[]{ this });
|
||||
d.Method.Invoke(d.Target, new object[] { this });
|
||||
statsStopwatch.Stop();
|
||||
var key = $"{d.Target}::{d.Method.Name}";
|
||||
if (notUpdated.Contains(key)) notUpdated.Remove(key);
|
||||
if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List<double>());
|
||||
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
||||
if (StatsHistory[key].Count > 1000) {
|
||||
if (StatsHistory[key].Count > 1000)
|
||||
{
|
||||
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup handlers that are no longer being called
|
||||
foreach (var key in notUpdated) {
|
||||
if (StatsHistory[key].Count > 0) {
|
||||
foreach (var key in notUpdated)
|
||||
{
|
||||
if (StatsHistory[key].Count > 0)
|
||||
{
|
||||
StatsHistory[key].RemoveAt(0);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
StatsHistory.Remove(key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
OnUpdateEvent?.Invoke(this);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
else
|
||||
{
|
||||
this.OnUpdateEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
||||
}
|
||||
}
|
||||
|
|
@ -199,7 +254,8 @@ namespace Dalamud.Game.Internal {
|
|||
return this.realDestroyHook.Original(framework);
|
||||
}
|
||||
|
||||
private IntPtr HandleFrameworkDestroy() {
|
||||
private IntPtr HandleFrameworkDestroy()
|
||||
{
|
||||
Log.Information("Framework::Free!");
|
||||
|
||||
// Store the pointer to the original trampoline location
|
||||
|
|
|
|||
|
|
@ -1,26 +1,43 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Internal {
|
||||
public sealed class FrameworkAddressResolver : BaseAddressResolver {
|
||||
namespace Dalamud.Game.Internal
|
||||
{
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native GuiManager class.
|
||||
/// </summary>
|
||||
public IntPtr GuiManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native ScriptManager class.
|
||||
/// </summary>
|
||||
public IntPtr ScriptManager { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
SetupFramework(sig);
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.SetupFramework(sig);
|
||||
|
||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
||||
GuiManager = Marshal.ReadIntPtr(BaseAddress, 0x2C08);
|
||||
this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08);
|
||||
|
||||
// Called from Framework::Init
|
||||
ScriptManager = BaseAddress + 0x2C68; // note that no deref here
|
||||
this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here
|
||||
}
|
||||
|
||||
private void SetupFramework(SigScanner scanner) {
|
||||
private void SetupFramework(SigScanner scanner)
|
||||
{
|
||||
// Dissasembly of part of the .dtor
|
||||
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
||||
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
|
||||
|
|
@ -32,7 +49,7 @@ namespace Dalamud.Game.Internal {
|
|||
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
||||
|
||||
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
||||
BaseAddress = Marshal.ReadIntPtr(pFramework);
|
||||
this.BaseAddress = Marshal.ReadIntPtr(pFramework);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui.Addon {
|
||||
public class Addon {
|
||||
namespace Dalamud.Game.Internal.Gui.Addon
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents an in-game UI "Addon".
|
||||
/// </summary>
|
||||
public class Addon
|
||||
{
|
||||
/// <summary>
|
||||
/// The address of the addon.
|
||||
/// </summary>
|
||||
public IntPtr Address;
|
||||
|
||||
/// <summary>
|
||||
/// The addon interop data.
|
||||
/// </summary>
|
||||
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.addonStruct = addonStruct;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,35 +3,127 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
public sealed class ChatGui : IDisposable {
|
||||
private readonly Queue<XivChatEntry> chatQueue = new Queue<XivChatEntry>();
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <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);
|
||||
public delegate void OnMessageRawDelegate(XivChatType type, uint senderId, ref StdString sender, ref StdString message, ref bool isHandled);
|
||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||
/// </summary>
|
||||
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>
|
||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
|
|
@ -43,101 +135,239 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
public event OnMessageUnhandledDelegate OnChatMessageUnhandled;
|
||||
|
||||
/// <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>
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags of the last linked item.
|
||||
/// </summary>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
||||
private ChatGuiAddressResolver Address { get; }
|
||||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
public ChatGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud) {
|
||||
this.dalamud = dalamud;
|
||||
|
||||
Address = new ChatGuiAddressResolver(baseAddress);
|
||||
Address.Setup(scanner);
|
||||
|
||||
Log.Verbose("Chat manager address {ChatManager}", Address.BaseAddress);
|
||||
|
||||
this.printMessageHook =
|
||||
new Hook<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() {
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) {
|
||||
try {
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="chat">A message to send.</param>
|
||||
public void PrintChat(XivChatEntry chat)
|
||||
{
|
||||
this.chatQueue.Enqueue(chat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(string message)
|
||||
{
|
||||
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);
|
||||
|
||||
LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{LastLinkedItemId}");
|
||||
} catch (Exception ex) {
|
||||
Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage,
|
||||
uint senderid, IntPtr parameter) {
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
var sender = StdString.ReadFromPointer(pSenderName);
|
||||
var message = StdString.ReadFromPointer(pMessage);
|
||||
|
||||
|
|
@ -146,23 +376,25 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
|
||||
Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
||||
|
||||
//Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||
|
||||
var originalMessageData = (byte[]) message.RawData.Clone();
|
||||
var originalMessageData = (byte[])message.RawData.Clone();
|
||||
var oldEdited = parsedMessage.Encode();
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
OnCheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
this.OnCheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
|
||||
if (!isHandled) {
|
||||
OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled);
|
||||
if (!isHandled)
|
||||
{
|
||||
this.OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
this.OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled);
|
||||
}
|
||||
|
||||
var newEdited = parsedMessage.Encode();
|
||||
|
||||
if (!FastByteArrayCompare(oldEdited, newEdited)) {
|
||||
if (!FastByteArrayCompare(oldEdited, newEdited))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
message.RawData = newEdited;
|
||||
Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
|
|
@ -171,7 +403,8 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
var messagePtr = pMessage;
|
||||
OwnedStdString allocatedString = null;
|
||||
|
||||
if (!FastByteArrayCompare(originalMessageData, message.RawData)) {
|
||||
if (!FastByteArrayCompare(originalMessageData, message.RawData))
|
||||
{
|
||||
allocatedString = this.dalamud.Framework.Libc.NewString(message.RawData);
|
||||
Log.Debug(
|
||||
$"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||
|
|
@ -181,19 +414,21 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
// Print the original chat if it's handled.
|
||||
if (isHandled)
|
||||
{
|
||||
OnChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
this.OnChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter);
|
||||
OnChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
this.OnChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
}
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
this.baseAddress = manager;
|
||||
|
||||
allocatedString?.Dispose();
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||
}
|
||||
|
|
@ -201,47 +436,14 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private readonly Dictionary<(string pluginName, uint commandId), Action<uint, SeString>> dalamudLinkHandlers = new Dictionary<(string, uint), Action<uint, SeString>>();
|
||||
|
||||
/// <summary>
|
||||
/// 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 {
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink) {
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
}
|
||||
|
|
@ -258,101 +460,23 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link) {
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) {
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||
{
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
}
|
||||
}
|
||||
|
||||
// Copyright (c) 2008-2013 Hafthor Stefansson
|
||||
// Distributed under the MIT/X11 software license
|
||||
// Ref: http://www.opensource.org/licenses/mit-license.php.
|
||||
// https://stackoverflow.com/a/8808245
|
||||
static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2)
|
||||
{
|
||||
if (a1 == a2) return true;
|
||||
if (a1 == null || a2 == null || a1.Length != a2.Length)
|
||||
return false;
|
||||
fixed (byte* p1 = a1, p2 = a2)
|
||||
{
|
||||
byte* x1 = p1, x2 = p2;
|
||||
int l = a1.Length;
|
||||
for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
|
||||
if (*((long*)x1) != *((long*)x2)) return false;
|
||||
if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; }
|
||||
if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; }
|
||||
if ((l & 1) != 0) if (*((byte*)x1) != *((byte*)x2)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver {
|
||||
public IntPtr BaseAddress { get; }
|
||||
|
||||
public IntPtr PrintMessage { get; private set; }
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
|
||||
public ChatGuiAddressResolver(IntPtr baseAddres) {
|
||||
BaseAddress = baseAddres;
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatGuiAddressResolver"/> class.
|
||||
/// </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 ---
|
||||
|
||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||
.text:00000001405CD210 var_210 = word ptr -210h
|
||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||
.text:00000001405CD210 var_200 = word ptr -200h
|
||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||
.text:00000001405CD210 push rbp
|
||||
.text:00000001405CD212 push rdi
|
||||
.text:00000001405CD213 push r14
|
||||
.text:00000001405CD215 push r15
|
||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||
.text:00000001405CD21F sub rsp, 228h
|
||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||
.text:00000001405CD22D xor rax, rsp
|
||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||
.text:00000001405CD237 xor r10b, r10b
|
||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||
.text:00000001405CD23F xor eax, eax
|
||||
.text:00000001405CD241 mov r11, r9
|
||||
.text:00000001405CD244 mov r14, r8
|
||||
.text:00000001405CD247 mov r9d, eax
|
||||
.text:00000001405CD24A movzx r15d, dx
|
||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||
.text:00000001405CD255 mov rdi, rcx
|
||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||
.text:00000001405CD210 var_210 = word ptr -210h
|
||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||
.text:00000001405CD210 var_200 = word ptr -200h
|
||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||
.text:00000001405CD210 push rbp
|
||||
.text:00000001405CD212 push rdi
|
||||
.text:00000001405CD213 push r14
|
||||
.text:00000001405CD215 push r15
|
||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||
.text:00000001405CD21F sub rsp, 228h
|
||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||
.text:00000001405CD22D xor rax, rsp
|
||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||
.text:00000001405CD237 xor r10b, r10b
|
||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||
.text:00000001405CD23F xor eax, eax
|
||||
.text:00000001405CD241 mov r11, r9
|
||||
.text:00000001405CD244 mov r14, r8
|
||||
.text:00000001405CD247 mov r9d, eax
|
||||
.text:00000001405CD24A movzx r15d, dx
|
||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||
.text:00000001405CD255 mov rdi, rcx
|
||||
*/
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
//PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
||||
PrintMessage =
|
||||
sig.ScanText(
|
||||
"4055 53 56 4154 4157 48 8d ac 24 ?? ?? ?? ?? 48 81 ec 20 02 00 00 48 8b 05"
|
||||
);
|
||||
//PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||
/// <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
|
||||
|
||||
//PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||
|
||||
//PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
//PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,84 +1,145 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
using SharpDX;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <summary>
|
||||
/// A class handling many aspects of the in-game UI.
|
||||
/// </summary>
|
||||
public sealed class GameGui : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The delegate of the native method that gets the Client::UI::UIModule address.
|
||||
/// </summary>
|
||||
/// <returns>The Client::UI::UIModule address.</returns>
|
||||
public readonly GetBaseUIObjectDelegate GetBaseUIObject;
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly GameGuiAddressResolver address;
|
||||
|
||||
private GameGuiAddressResolver Address { get; }
|
||||
private readonly GetMatrixSingletonDelegate getMatrixSingleton;
|
||||
private readonly GetUIObjectDelegate getUIObject;
|
||||
private readonly ScreenToWorldNativeDelegate screenToWorldNative;
|
||||
private readonly GetUIObjectByNameDelegate getUIObjectByName;
|
||||
private readonly GetUiModuleDelegate getUiModule;
|
||||
private readonly GetAgentModuleDelegate getAgentModule;
|
||||
|
||||
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;
|
||||
private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
|
||||
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
|
||||
private Hook<HandleActionOutDelegate> handleActionOutHook;
|
||||
private GetUIMapObjectDelegate getUIMapObject;
|
||||
private OpenMapWithFlagDelegate openMapWithFlag;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="baseAddress">The base address of the native GuiManager class.</param>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
/// <param name="dalamud">The Dalamud instance.</param>
|
||||
public GameGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
|
||||
this.address = new GameGuiAddressResolver(baseAddress);
|
||||
this.address.Setup(scanner);
|
||||
|
||||
Log.Verbose("===== G A M E G U I =====");
|
||||
|
||||
Log.Verbose("GameGuiManager address {Address:X}", this.address.BaseAddress.ToInt64());
|
||||
Log.Verbose("SetGlobalBgm address {Address:X}", this.address.SetGlobalBgm.ToInt64());
|
||||
Log.Verbose("HandleItemHover address {Address:X}", this.address.HandleItemHover.ToInt64());
|
||||
Log.Verbose("HandleItemOut address {Address:X}", this.address.HandleItemOut.ToInt64());
|
||||
Log.Verbose("GetUIObject address {Address:X}", this.address.GetUIObject.ToInt64());
|
||||
Log.Verbose("GetAgentModule address {Address:X}", this.address.GetAgentModule.ToInt64());
|
||||
|
||||
this.Chat = new ChatGui(this.address.ChatManager, scanner, dalamud);
|
||||
this.PartyFinder = new PartyFinderGui(scanner, dalamud);
|
||||
this.Toast = new ToastGui(scanner, dalamud);
|
||||
|
||||
this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, new SetGlobalBgmDelegate(this.HandleSetGlobalBgmDetour), this);
|
||||
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, new HandleItemHoverDelegate(this.HandleItemHoverDetour), this);
|
||||
|
||||
this.handleItemOutHook = new Hook<HandleItemOutDelegate>(this.address.HandleItemOut, new HandleItemOutDelegate(this.HandleItemOutDetour), this);
|
||||
|
||||
this.handleActionHoverHook = new Hook<HandleActionHoverDelegate>(this.address.HandleActionHover, new HandleActionHoverDelegate(this.HandleActionHoverDetour), this);
|
||||
this.handleActionOutHook = new Hook<HandleActionOutDelegate>(this.address.HandleActionOut, new HandleActionOutDelegate(this.HandleActionOutDetour), this);
|
||||
|
||||
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(this.address.GetUIObject);
|
||||
|
||||
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton);
|
||||
|
||||
this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer<ScreenToWorldNativeDelegate>(this.address.ScreenToWorld);
|
||||
|
||||
this.toggleUiHideHook = new Hook<ToggleUiHideDelegate>(this.address.ToggleUiHide, new ToggleUiHideDelegate(this.ToggleUiHideDetour), this);
|
||||
|
||||
this.GetBaseUIObject = Marshal.GetDelegateForFunctionPointer<GetBaseUIObjectDelegate>(this.address.GetBaseUIObject);
|
||||
this.getUIObjectByName = Marshal.GetDelegateForFunctionPointer<GetUIObjectByNameDelegate>(this.address.GetUIObjectByName);
|
||||
|
||||
this.getUiModule = Marshal.GetDelegateForFunctionPointer<GetUiModuleDelegate>(this.address.GetUIModule);
|
||||
this.getAgentModule = Marshal.GetDelegateForFunctionPointer<GetAgentModuleDelegate>(this.address.GetAgentModule);
|
||||
}
|
||||
|
||||
// Marshaled delegates
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type of the native method that gets the Client::UI::UIModule address.
|
||||
/// </summary>
|
||||
/// <returns>The Client::UI::UIModule address.</returns>
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate IntPtr GetBaseUIObjectDelegate();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate IntPtr GetMatrixSingletonDelegate();
|
||||
|
||||
[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;
|
||||
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 readonly GetUIObjectByNameDelegate getUIObjectByName;
|
||||
|
||||
private delegate IntPtr GetUiModuleDelegate(IntPtr basePtr);
|
||||
private readonly GetUiModuleDelegate getUiModule;
|
||||
|
||||
private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule);
|
||||
private GetAgentModuleDelegate getAgentModule;
|
||||
|
||||
public bool GameUiHidden { get; private set; }
|
||||
// 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.
|
||||
|
|
@ -86,207 +147,72 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
public event EventHandler<bool> OnUiHideToggled;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// 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>
|
||||
/// The action ID that is current hovered by the player. 0 when no action is hovered.
|
||||
/// 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>
|
||||
/// Event that is fired when the currently hovered item changes.
|
||||
/// Gets or sets the 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.
|
||||
/// Gets or sets the 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)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
|
||||
Address = new GameGuiAddressResolver(baseAddress);
|
||||
Address.Setup(scanner);
|
||||
|
||||
Log.Verbose("===== G A M E G U I =====");
|
||||
|
||||
Log.Verbose("GameGuiManager address {Address}", Address.BaseAddress);
|
||||
Log.Verbose("SetGlobalBgm address {Address}", Address.SetGlobalBgm);
|
||||
Log.Verbose("HandleItemHover address {Address}", Address.HandleItemHover);
|
||||
Log.Verbose("HandleItemOut address {Address}", Address.HandleItemOut);
|
||||
Log.Verbose("GetUIObject address {Address}", Address.GetUIObject);
|
||||
Log.Verbose("GetAgentModule address {Address}", Address.GetAgentModule);
|
||||
|
||||
Chat = new ChatGui(Address.ChatManager, scanner, dalamud);
|
||||
PartyFinder = new PartyFinderGui(scanner, dalamud);
|
||||
Toast = new ToastGui(scanner, dalamud);
|
||||
|
||||
this.setGlobalBgmHook =
|
||||
new Hook<SetGlobalBgmDelegate>(Address.SetGlobalBgm,
|
||||
new SetGlobalBgmDelegate(HandleSetGlobalBgmDetour),
|
||||
this);
|
||||
this.handleItemHoverHook =
|
||||
new Hook<HandleItemHoverDelegate>(Address.HandleItemHover,
|
||||
new HandleItemHoverDelegate(HandleItemHoverDetour),
|
||||
this);
|
||||
|
||||
this.handleItemOutHook =
|
||||
new Hook<HandleItemOutDelegate>(Address.HandleItemOut,
|
||||
new HandleItemOutDelegate(HandleItemOutDetour),
|
||||
this);
|
||||
|
||||
this.handleActionHoverHook =
|
||||
new Hook<HandleActionHoverDelegate>(Address.HandleActionHover,
|
||||
new HandleActionHoverDelegate(HandleActionHoverDetour),
|
||||
this);
|
||||
this.handleActionOutHook =
|
||||
new Hook<HandleActionOutDelegate>(Address.HandleActionOut,
|
||||
new HandleActionOutDelegate(HandleActionOutDetour),
|
||||
this);
|
||||
|
||||
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(Address.GetUIObject);
|
||||
|
||||
this.getMatrixSingleton =
|
||||
Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(Address.GetMatrixSingleton);
|
||||
|
||||
this.screenToWorldNative =
|
||||
Marshal.GetDelegateForFunctionPointer<ScreenToWorldNativeDelegate>(Address.ScreenToWorld);
|
||||
|
||||
this.toggleUiHideHook = new Hook<ToggleUiHideDelegate>(Address.ToggleUiHide, new ToggleUiHideDelegate(ToggleUiHideDetour), this);
|
||||
|
||||
this.GetBaseUIObject = Marshal.GetDelegateForFunctionPointer<GetBaseUIObjectDelegate>(Address.GetBaseUIObject);
|
||||
this.getUIObjectByName = Marshal.GetDelegateForFunctionPointer<GetUIObjectByNameDelegate>(Address.GetUIObjectByName);
|
||||
|
||||
this.getUiModule = Marshal.GetDelegateForFunctionPointer<GetUiModuleDelegate>(Address.GetUIModule);
|
||||
this.getAgentModule = Marshal.GetDelegateForFunctionPointer<GetAgentModuleDelegate>(Address.GetAgentModule);
|
||||
}
|
||||
|
||||
private IntPtr HandleSetGlobalBgmDetour(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6) {
|
||||
var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
|
||||
|
||||
Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) {
|
||||
var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (retVal.ToInt64() == 22) {
|
||||
var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
|
||||
this.HoveredItem = itemId;
|
||||
|
||||
try {
|
||||
HoveredItemChanged?.Invoke(this, itemId);
|
||||
} catch (Exception e) {
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X"));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||
{
|
||||
var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (a3 != IntPtr.Zero && a4 == 1) {
|
||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||
|
||||
if (a3Val == 255) {
|
||||
this.HoveredItem = 0ul;
|
||||
|
||||
try {
|
||||
HoveredItemChanged?.Invoke(this, 0ul);
|
||||
} catch (Exception e) {
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverItemId: 0");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
|
||||
{
|
||||
handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
|
||||
HoveredAction.ActionKind = actionKind;
|
||||
HoveredAction.BaseActionID = actionId;
|
||||
HoveredAction.ActionID = (uint) Marshal.ReadInt32(hoverState, 0x3C);
|
||||
try
|
||||
{
|
||||
HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
||||
} catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X"));
|
||||
}
|
||||
|
||||
private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
|
||||
{
|
||||
var retVal = handleActionOutHook.Original(agentActionDetail, a2, a3, a4);
|
||||
|
||||
if (a3 != IntPtr.Zero && a4 == 1)
|
||||
{
|
||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||
|
||||
if (a3Val == 255)
|
||||
{
|
||||
this.HoveredAction.ActionKind = HoverActionKind.None;
|
||||
HoveredAction.BaseActionID = 0;
|
||||
HoveredAction.ActionID = 0;
|
||||
|
||||
try
|
||||
{
|
||||
HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
||||
} catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredActionChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverActionId: 0");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the in-game map with a flag on the location of the parameter
|
||||
/// 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) {
|
||||
/// <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();
|
||||
|
||||
if (uiObjectPtr.Equals(IntPtr.Zero)) {
|
||||
if (uiObjectPtr.Equals(IntPtr.Zero))
|
||||
{
|
||||
Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.getUIMapObject =
|
||||
Address.GetVirtualFunction<GetUIMapObjectDelegate>(uiObjectPtr, 0, 8);
|
||||
|
||||
this.getUIMapObject = this.address.GetVirtualFunction<GetUIMapObjectDelegate>(uiObjectPtr, 0, 8);
|
||||
|
||||
var uiMapObjectPtr = this.getUIMapObject(uiObjectPtr);
|
||||
|
||||
if (uiMapObjectPtr.Equals(IntPtr.Zero)) {
|
||||
if (uiMapObjectPtr.Equals(IntPtr.Zero))
|
||||
{
|
||||
Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.openMapWithFlag =
|
||||
Address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
||||
this.openMapWithFlag = this.address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
||||
|
||||
var mapLinkString = mapLink.DataString;
|
||||
|
||||
|
|
@ -298,21 +224,22 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
/// <summary>
|
||||
/// Converts in-world coordinates to screen coordinates (upper left corner origin).
|
||||
/// </summary>
|
||||
/// <param name="worldPos">Coordinates in the world</param>
|
||||
/// <param name="screenPos">Converted coordinates</param>
|
||||
/// <returns>True if worldPos corresponds to a position in front of the camera</returns>
|
||||
/// <param name="worldPos">Coordinates in the world.</param>
|
||||
/// <param name="screenPos">Converted coordinates.</param>
|
||||
/// <returns>True if worldPos corresponds to a position in front of the camera.</returns>
|
||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
|
||||
{
|
||||
// Get base object with matrices
|
||||
var matrixSingleton = this.getMatrixSingleton();
|
||||
|
||||
// Read current ViewProjectionMatrix plus game window size
|
||||
var viewProjectionMatrix = new Matrix();
|
||||
var viewProjectionMatrix = default(Matrix);
|
||||
float width, height;
|
||||
var windowPos = ImGuiHelpers.MainViewport.Pos;
|
||||
|
||||
unsafe {
|
||||
var rawMatrix = (float*) (matrixSingleton + 0x1b4).ToPointer();
|
||||
unsafe
|
||||
{
|
||||
var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer();
|
||||
|
||||
for (var i = 0; i < 16; i++, rawMatrix++)
|
||||
viewProjectionMatrix[i] = *rawMatrix;
|
||||
|
|
@ -321,12 +248,12 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
height = *(rawMatrix + 1);
|
||||
}
|
||||
|
||||
Vector3.Transform( ref worldPos, ref viewProjectionMatrix, out Vector3 pCoords);
|
||||
Vector3.Transform(ref worldPos, ref viewProjectionMatrix, out Vector3 pCoords);
|
||||
|
||||
screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z);
|
||||
|
||||
screenPos.X = 0.5f * width * (screenPos.X + 1f) + windowPos.X;
|
||||
screenPos.Y = 0.5f * height * (1f - screenPos.Y) + windowPos.Y;
|
||||
screenPos.X = (0.5f * width * (screenPos.X + 1f)) + windowPos.X;
|
||||
screenPos.Y = (0.5f * height * (1f - screenPos.Y)) + windowPos.Y;
|
||||
|
||||
return pCoords.Z > 0 &&
|
||||
screenPos.X > windowPos.X && screenPos.X < windowPos.X + width &&
|
||||
|
|
@ -336,10 +263,10 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
/// <summary>
|
||||
/// Converts screen coordinates to in-world coordinates via raycasting.
|
||||
/// </summary>
|
||||
/// <param name="screenPos">Screen coordinates</param>
|
||||
/// <param name="worldPos">Converted coordinates</param>
|
||||
/// <param name="rayDistance">How far to search for a collision</param>
|
||||
/// <returns>True if successful. On false, worldPos's contents are undefined</returns>
|
||||
/// <param name="screenPos">Screen coordinates.</param>
|
||||
/// <param name="worldPos">Converted coordinates.</param>
|
||||
/// <param name="rayDistance">How far to search for a collision.</param>
|
||||
/// <returns>True if successful. On false, worldPos's contents are undefined.</returns>
|
||||
public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f)
|
||||
{
|
||||
// The game is only visible in the main viewport, so if the cursor is outside
|
||||
|
|
@ -350,7 +277,7 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X ||
|
||||
screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y)
|
||||
{
|
||||
worldPos = new Vector3();
|
||||
worldPos = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -358,7 +285,7 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
var matrixSingleton = this.getMatrixSingleton();
|
||||
|
||||
// Read current ViewProjectionMatrix plus game window size
|
||||
var viewProjectionMatrix = new Matrix();
|
||||
var viewProjectionMatrix = default(Matrix);
|
||||
float width, height;
|
||||
unsafe
|
||||
{
|
||||
|
|
@ -374,10 +301,11 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
viewProjectionMatrix.Invert();
|
||||
|
||||
var localScreenPos = new Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y);
|
||||
var screenPos3D = new Vector3 {
|
||||
X = localScreenPos.X / width * 2.0f - 1.0f,
|
||||
Y = -(localScreenPos.Y / height * 2.0f - 1.0f),
|
||||
Z = 0
|
||||
var screenPos3D = new Vector3
|
||||
{
|
||||
X = (localScreenPos.X / width * 2.0f) - 1.0f,
|
||||
Y = -((localScreenPos.Y / height * 2.0f) - 1.0f),
|
||||
Z = 0,
|
||||
};
|
||||
|
||||
Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos);
|
||||
|
|
@ -389,7 +317,8 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
clipPos.Normalize();
|
||||
|
||||
bool isSuccess;
|
||||
unsafe {
|
||||
unsafe
|
||||
{
|
||||
var camPosArray = camPos.ToArray();
|
||||
var clipPosArray = clipPos.ToArray();
|
||||
|
||||
|
|
@ -397,54 +326,46 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
var worldPosArray = stackalloc float[32];
|
||||
|
||||
// Theory: this is some kind of flag on what type of things the ray collides with
|
||||
var unknown = stackalloc int[3] { 0x4000, 0x4000, 0x0 };
|
||||
var unknown = stackalloc int[3]
|
||||
{
|
||||
0x4000,
|
||||
0x4000,
|
||||
0x0,
|
||||
};
|
||||
|
||||
fixed (float* pCamPos = camPosArray) {
|
||||
fixed (float* pClipPos = clipPosArray) {
|
||||
fixed (float* pCamPos = camPosArray)
|
||||
{
|
||||
fixed (float* pClipPos = clipPosArray)
|
||||
{
|
||||
isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown);
|
||||
}
|
||||
}
|
||||
|
||||
worldPos = new Vector3 {
|
||||
worldPos = new Vector3
|
||||
{
|
||||
X = worldPosArray[0],
|
||||
Y = worldPosArray[1],
|
||||
Z = worldPosArray[2]
|
||||
Z = worldPosArray[2],
|
||||
};
|
||||
}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) {
|
||||
GameUiHidden = !GameUiHidden;
|
||||
|
||||
try {
|
||||
OnUiHideToggled?.Invoke(this, GameUiHidden);
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Error on OnUiHideToggled event dispatch");
|
||||
}
|
||||
|
||||
Log.Debug("UiHide toggled: {0}", GameUiHidden);
|
||||
|
||||
return this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a pointer to the game's UI module.
|
||||
/// </summary>
|
||||
/// <returns>IntPtr pointing to UI module</returns>
|
||||
public IntPtr GetUIModule()
|
||||
{
|
||||
return this.getUiModule(this.dalamud.Framework.Address.BaseAddress);
|
||||
}
|
||||
/// <returns>IntPtr pointing to UI module.</returns>
|
||||
public IntPtr GetUIModule() => this.getUiModule(this.dalamud.Framework.Address.BaseAddress);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the UI Object with the given name and index.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of UI to find</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>
|
||||
public IntPtr GetUiObjectByName(string name, int index) {
|
||||
/// <param name="name">Name of UI to find.</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>
|
||||
public IntPtr GetUiObjectByName(string name, int index)
|
||||
{
|
||||
var baseUi = this.GetBaseUIObject();
|
||||
if (baseUi == IntPtr.Zero) return IntPtr.Zero;
|
||||
var baseUiProperties = Marshal.ReadIntPtr(baseUi, 0x20);
|
||||
|
|
@ -452,19 +373,36 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
return this.getUIObjectByName(baseUiProperties, name, index);
|
||||
}
|
||||
|
||||
public Addon.Addon GetAddonByName(string name, int index) {
|
||||
var addonMem = GetUiObjectByName(name, index);
|
||||
/// <summary>
|
||||
/// 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;
|
||||
var addonStruct = Marshal.PtrToStructure<Structs.Addon>(addonMem);
|
||||
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)
|
||||
{
|
||||
var addon = this.dalamud.Framework.Gui.GetUiObjectByName(addonName, 1);
|
||||
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)
|
||||
{
|
||||
if (addon == IntPtr.Zero)
|
||||
|
|
@ -501,12 +439,20 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <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() {
|
||||
Chat.Enable();
|
||||
Toast.Enable();
|
||||
PartyFinder.Enable();
|
||||
/// <summary>
|
||||
/// Enables the hooks and submodules of this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.Chat.Enable();
|
||||
this.Toast.Enable();
|
||||
this.PartyFinder.Enable();
|
||||
this.setGlobalBgmHook.Enable();
|
||||
this.handleItemHoverHook.Enable();
|
||||
this.handleItemOutHook.Enable();
|
||||
|
|
@ -515,10 +461,14 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
this.handleActionOutHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Chat.Dispose();
|
||||
Toast.Dispose();
|
||||
PartyFinder.Dispose();
|
||||
/// <summary>
|
||||
/// Disables the hooks and submodules of this module.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Chat.Dispose();
|
||||
this.Toast.Dispose();
|
||||
this.PartyFinder.Dispose();
|
||||
this.setGlobalBgmHook.Dispose();
|
||||
this.handleItemHoverHook.Dispose();
|
||||
this.handleItemOutHook.Dispose();
|
||||
|
|
@ -526,5 +476,132 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
this.handleActionHoverHook.Dispose();
|
||||
this.handleActionOutHook.Dispose();
|
||||
}
|
||||
|
||||
private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6)
|
||||
{
|
||||
var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
|
||||
|
||||
Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||
{
|
||||
var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (retVal.ToInt64() == 22)
|
||||
{
|
||||
var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
|
||||
this.HoveredItem = itemId;
|
||||
|
||||
try
|
||||
{
|
||||
this.HoveredItemChanged?.Invoke(this, itemId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X"));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||
{
|
||||
var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (a3 != IntPtr.Zero && a4 == 1)
|
||||
{
|
||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||
|
||||
if (a3Val == 255)
|
||||
{
|
||||
this.HoveredItem = 0ul;
|
||||
|
||||
try
|
||||
{
|
||||
this.HoveredItemChanged?.Invoke(this, 0ul);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverItemId: 0");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
|
||||
{
|
||||
this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
|
||||
this.HoveredAction.ActionKind = actionKind;
|
||||
this.HoveredAction.BaseActionID = actionId;
|
||||
this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C);
|
||||
try
|
||||
{
|
||||
this.HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X"));
|
||||
}
|
||||
|
||||
private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
|
||||
{
|
||||
var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4);
|
||||
|
||||
if (a3 != IntPtr.Zero && a4 == 1)
|
||||
{
|
||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||
|
||||
if (a3Val == 255)
|
||||
{
|
||||
this.HoveredAction.ActionKind = HoverActionKind.None;
|
||||
this.HoveredAction.BaseActionID = 0;
|
||||
this.HoveredAction.ActionID = 0;
|
||||
|
||||
try
|
||||
{
|
||||
this.HoveredActionChanged?.Invoke(this, this.HoveredAction);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredActionChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverActionId: 0");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte)
|
||||
{
|
||||
this.GameUiHidden = !this.GameUiHidden;
|
||||
|
||||
try
|
||||
{
|
||||
this.OnUiHideToggled?.Invoke(this, this.GameUiHidden);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error on OnUiHideToggled event dispatch");
|
||||
}
|
||||
|
||||
Log.Debug("UiHide toggled: {0}", this.GameUiHidden);
|
||||
|
||||
return this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,123 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver {
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ChatManager class.
|
||||
/// </summary>
|
||||
public IntPtr ChatManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native SetGlobalBgm method.
|
||||
/// </summary>
|
||||
public IntPtr SetGlobalBgm { get; private set; }
|
||||
public IntPtr HandleItemHover { get; set; }
|
||||
public IntPtr HandleItemOut { get; set; }
|
||||
public IntPtr HandleActionHover { get; set; }
|
||||
public IntPtr HandleActionOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetMatrixSingleton method.
|
||||
/// </summary>
|
||||
public IntPtr GetMatrixSingleton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ScreenToWorld method.
|
||||
/// </summary>
|
||||
public IntPtr ScreenToWorld { get; private set; }
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetUIObjectByName method.
|
||||
/// </summary>
|
||||
public IntPtr GetUIObjectByName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetUIModule method.
|
||||
/// </summary>
|
||||
public IntPtr GetUIModule { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetAgentModule method.
|
||||
/// </summary>
|
||||
public IntPtr GetAgentModule { get; private set; }
|
||||
|
||||
public GameGuiAddressResolver(IntPtr baseAddress) {
|
||||
BaseAddress = baseAddress;
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetChatManagerDelegate(IntPtr guiManager);
|
||||
|
||||
protected override void SetupInternal(SigScanner scanner) {
|
||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
||||
ChatManager = BaseAddress + 0x13E0;
|
||||
}
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||
HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||
HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
|
||||
GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
GetBaseUIObject = sig.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
|
||||
GetUIObjectByName = sig.ScanText("E8 ?? ?? ?? ?? 48 8B CF 48 89 87 ?? ?? 00 00 E8 ?? ?? ?? ?? 41 B8 01 00 00 00");
|
||||
GetUIModule = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 85 C0 75 2D");
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
|
||||
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
this.GetBaseUIObject = sig.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
|
||||
this.GetUIObjectByName = sig.ScanText("E8 ?? ?? ?? ?? 48 8B CF 48 89 87 ?? ?? 00 00 E8 ?? ?? ?? ?? 41 B8 01 00 00 00");
|
||||
this.GetUIModule = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 85 C0 75 2D");
|
||||
|
||||
var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28");
|
||||
this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// ActionKinds used in AgentActionDetail.
|
||||
/// These describe the possible kinds of actions being hovered.
|
||||
/// </summary>
|
||||
public enum HoverActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// No action is hovered.
|
||||
/// </summary>
|
||||
public enum HoverActionKind {
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A regular action is hovered.
|
||||
/// </summary>
|
||||
Action = 21,
|
||||
|
||||
/// <summary>
|
||||
/// A general action is hovered.
|
||||
/// </summary>
|
||||
GeneralAction = 23,
|
||||
|
||||
/// <summary>
|
||||
/// A companion order type of action is hovered.
|
||||
/// </summary>
|
||||
CompanionOrder = 24,
|
||||
|
||||
/// <summary>
|
||||
/// A main command type of action is hovered.
|
||||
/// </summary>
|
||||
MainCommand = 25,
|
||||
|
||||
/// <summary>
|
||||
/// An extras command type of action is hovered.
|
||||
/// </summary>
|
||||
ExtraCommand = 26,
|
||||
|
||||
/// <summary>
|
||||
/// A pet order type of action is hovered.
|
||||
/// </summary>
|
||||
PetOrder = 28,
|
||||
|
||||
/// <summary>
|
||||
/// A trait is hovered.
|
||||
/// </summary>
|
||||
Trait = 29,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
namespace Dalamud.Game.Internal.Gui {
|
||||
public class HoveredAction {
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <summary>
|
||||
/// The base action ID
|
||||
/// This class represents the hotbar action currently hovered over by the cursor.
|
||||
/// </summary>
|
||||
public class HoveredAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the base action ID.
|
||||
/// </summary>
|
||||
public uint BaseActionID { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Action ID accounting for automatic upgrades.
|
||||
/// Gets or sets the action ID accounting for automatic upgrades.
|
||||
/// </summary>
|
||||
public uint ActionID { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The type of action
|
||||
/// Gets or sets the type of action.
|
||||
/// </summary>
|
||||
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
class PartyFinderAddressResolver : BaseAddressResolver {
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <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; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,71 +1,90 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Internal.Gui.Structs;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
public sealed class PartyFinderGui : IDisposable {
|
||||
#region Events
|
||||
|
||||
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <summary>
|
||||
/// Event fired each time the game receives an individual Party Finder listing. Cannot modify listings but can
|
||||
/// hide them.
|
||||
/// This class handles interacting with the native PartyFinder window.
|
||||
/// </summary>
|
||||
public event PartyFinderListingEventDelegate ReceiveListing;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hooks
|
||||
public sealed class PartyFinderGui : IDisposable
|
||||
{
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly IntPtr memory;
|
||||
|
||||
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)]
|
||||
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; }
|
||||
private PartyFinderAddressResolver Address { get; }
|
||||
private IntPtr Memory { get; }
|
||||
|
||||
public PartyFinderGui(SigScanner scanner, Dalamud dalamud) {
|
||||
Dalamud = dalamud;
|
||||
|
||||
Address = new PartyFinderAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
|
||||
Memory = Marshal.AllocHGlobal(PartyFinder.PacketInfo.PacketSize);
|
||||
|
||||
this.receiveListingHook = new Hook<ReceiveListingDelegate>(Address.ReceiveListing, new ReceiveListingDelegate(HandleReceiveListingDetour));
|
||||
}
|
||||
|
||||
public void Enable() {
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.receiveListingHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
/// <summary>
|
||||
/// Dispose of m anaged and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.receiveListingHook.Dispose();
|
||||
Marshal.FreeHGlobal(Memory);
|
||||
Marshal.FreeHGlobal(this.memory);
|
||||
}
|
||||
|
||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) {
|
||||
try {
|
||||
HandleListingEvents(data);
|
||||
} catch (Exception ex) {
|
||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HandleListingEvents(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||
}
|
||||
|
||||
this.receiveListingHook.Original(managerPtr, data);
|
||||
}
|
||||
|
||||
private void HandleListingEvents(IntPtr data) {
|
||||
private void HandleListingEvents(IntPtr data)
|
||||
{
|
||||
var dataPtr = data + 0x10;
|
||||
|
||||
var packet = Marshal.PtrToStructure<PartyFinder.Packet>(dataPtr);
|
||||
|
|
@ -73,51 +92,66 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
// rewriting is an expensive operation, so only do it if necessary
|
||||
var needToRewrite = false;
|
||||
|
||||
for (var i = 0; i < packet.listings.Length; i++) {
|
||||
for (var i = 0; i < packet.Listings.Length; i++)
|
||||
{
|
||||
// these are empty slots that are not shown to the player
|
||||
if (packet.listings[i].IsNull()) {
|
||||
if (packet.Listings[i].IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var listing = new PartyFinderListing(packet.listings[i], Dalamud.Data, Dalamud.SeStringManager);
|
||||
var args = new PartyFinderListingEventArgs(packet.batchNumber);
|
||||
ReceiveListing?.Invoke(listing, args);
|
||||
var listing = new PartyFinderListing(packet.Listings[i], this.dalamud.Data, this.dalamud.SeStringManager);
|
||||
var args = new PartyFinderListingEventArgs(packet.BatchNumber);
|
||||
this.ReceiveListing?.Invoke(listing, args);
|
||||
|
||||
if (args.Visible) {
|
||||
if (args.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// hide the listing from the player by setting it to a null listing
|
||||
packet.listings[i] = new PartyFinder.Listing();
|
||||
packet.Listings[i] = default;
|
||||
needToRewrite = true;
|
||||
}
|
||||
|
||||
if (!needToRewrite) {
|
||||
if (!needToRewrite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// write our struct into the memory (doing this directly crashes the game)
|
||||
Marshal.StructureToPtr(packet, Memory, false);
|
||||
Marshal.StructureToPtr(packet, this.memory, false);
|
||||
|
||||
// copy our new memory over the game's
|
||||
unsafe {
|
||||
Buffer.MemoryCopy(
|
||||
(void*) Memory,
|
||||
(void*) dataPtr,
|
||||
PartyFinder.PacketInfo.PacketSize,
|
||||
PartyFinder.PacketInfo.PacketSize
|
||||
);
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinder.PacketInfo.PacketSize, PartyFinder.PacketInfo.PacketSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PartyFinderListingEventArgs {
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the listing is visible.
|
||||
/// </summary>
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
internal PartyFinderListingEventArgs(int batchNumber) {
|
||||
BatchNumber = batchNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,59 @@
|
|||
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 RootNode = 0xC8;
|
||||
public const int Flags = 0x182;
|
||||
|
|
@ -10,17 +61,4 @@ namespace Dalamud.Game.Internal.Gui.Structs {
|
|||
public const int Y = 0x1BE;
|
||||
public const int Scale = 0x1AC;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Addon {
|
||||
[FieldOffset(AddonOffsets.Name), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
|
||||
public string Name;
|
||||
|
||||
[FieldOffset(AddonOffsets.Flags)] public byte Flags;
|
||||
[FieldOffset(AddonOffsets.X)] public short X;
|
||||
[FieldOffset(AddonOffsets.Y)] public short Y;
|
||||
[FieldOffset(AddonOffsets.Scale)] public float Scale;
|
||||
[FieldOffset(AddonOffsets.RootNode)] public unsafe AtkResNode* RootNode;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,130 @@
|
|||
using System;
|
||||
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)]
|
||||
public unsafe struct AtkResNode
|
||||
{
|
||||
[FieldOffset(0x0)]
|
||||
public IntPtr AtkEventTarget;
|
||||
|
||||
// https://github.com/aers/FFXIVClientStructs/blob/main/Component/GUI/AtkResNode.cs
|
||||
public unsafe struct AtkResNode {
|
||||
[FieldOffset(0x0)] public IntPtr AtkEventTarget;
|
||||
[FieldOffset(0x8)] public uint NodeID;
|
||||
[FieldOffset(0x20)] public AtkResNode* ParentNode;
|
||||
[FieldOffset(0x28)] public AtkResNode* PrevSiblingNode;
|
||||
[FieldOffset(0x30)] public AtkResNode* NextSiblingNode;
|
||||
[FieldOffset(0x38)] public AtkResNode* ChildNode;
|
||||
[FieldOffset(0x40)] public ushort Type;
|
||||
[FieldOffset(0x42)] public ushort ChildCount;
|
||||
[FieldOffset(0x44)] public float X;
|
||||
[FieldOffset(0x48)] public float Y;
|
||||
[FieldOffset(0x4C)] public float ScaleX;
|
||||
[FieldOffset(0x50)] public float ScaleY;
|
||||
[FieldOffset(0x54)] public float Rotation;
|
||||
[FieldOffset(0x58)] public fixed float UnkMatrix[3 * 2];
|
||||
[FieldOffset(0x70)] public uint Color;
|
||||
[FieldOffset(0x74)] public float Depth;
|
||||
[FieldOffset(0x78)] public float Depth_2;
|
||||
[FieldOffset(0x7C)] public ushort AddRed;
|
||||
[FieldOffset(0x7E)] public ushort AddGreen;
|
||||
[FieldOffset(0x80)] public ushort AddBlue;
|
||||
[FieldOffset(0x82)] public ushort AddRed_2;
|
||||
[FieldOffset(0x84)] public ushort AddGreen_2;
|
||||
[FieldOffset(0x86)] public ushort AddBlue_2;
|
||||
[FieldOffset(0x88)] public byte MultiplyRed;
|
||||
[FieldOffset(0x89)] public byte MultiplyGreen;
|
||||
[FieldOffset(0x8A)] public byte MultiplyBlue;
|
||||
[FieldOffset(0x8B)] public byte MultiplyRed_2;
|
||||
[FieldOffset(0x8C)] public byte MultiplyGreen_2;
|
||||
[FieldOffset(0x8D)] public byte MultiplyBlue_2;
|
||||
[FieldOffset(0x8E)] public byte Alpha_2;
|
||||
[FieldOffset(0x8F)] public byte UnkByte_1;
|
||||
[FieldOffset(0x90)] public ushort Width;
|
||||
[FieldOffset(0x92)] public ushort Height;
|
||||
[FieldOffset(0x94)] public float OriginX;
|
||||
[FieldOffset(0x98)] public float OriginY;
|
||||
[FieldOffset(0x9C)] public ushort Priority;
|
||||
[FieldOffset(0x9E)] public short Flags;
|
||||
[FieldOffset(0xA0)] public uint Flags_2;
|
||||
[FieldOffset(0x8)]
|
||||
public uint NodeID;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public AtkResNode* ParentNode;
|
||||
|
||||
[FieldOffset(0x28)]
|
||||
public AtkResNode* PrevSiblingNode;
|
||||
|
||||
[FieldOffset(0x30)]
|
||||
public AtkResNode* NextSiblingNode;
|
||||
|
||||
[FieldOffset(0x38)]
|
||||
public AtkResNode* ChildNode;
|
||||
|
||||
[FieldOffset(0x40)]
|
||||
public ushort Type;
|
||||
|
||||
[FieldOffset(0x42)]
|
||||
public ushort ChildCount;
|
||||
|
||||
[FieldOffset(0x44)]
|
||||
public float X;
|
||||
|
||||
[FieldOffset(0x48)]
|
||||
public float Y;
|
||||
|
||||
[FieldOffset(0x4C)]
|
||||
public float ScaleX;
|
||||
|
||||
[FieldOffset(0x50)]
|
||||
public float ScaleY;
|
||||
|
||||
[FieldOffset(0x54)]
|
||||
public float Rotation;
|
||||
|
||||
[FieldOffset(0x58)]
|
||||
public fixed float UnkMatrix[3 * 2];
|
||||
|
||||
[FieldOffset(0x70)]
|
||||
public uint Color;
|
||||
|
||||
[FieldOffset(0x74)]
|
||||
public float Depth;
|
||||
|
||||
[FieldOffset(0x78)]
|
||||
public float Depth_2;
|
||||
|
||||
[FieldOffset(0x7C)]
|
||||
public ushort AddRed;
|
||||
|
||||
[FieldOffset(0x7E)]
|
||||
public ushort AddGreen;
|
||||
|
||||
[FieldOffset(0x80)]
|
||||
public ushort AddBlue;
|
||||
|
||||
[FieldOffset(0x82)]
|
||||
public ushort AddRed_2;
|
||||
|
||||
[FieldOffset(0x84)]
|
||||
public ushort AddGreen_2;
|
||||
|
||||
[FieldOffset(0x86)]
|
||||
public ushort AddBlue_2;
|
||||
|
||||
[FieldOffset(0x88)]
|
||||
public byte MultiplyRed;
|
||||
|
||||
[FieldOffset(0x89)]
|
||||
public byte MultiplyGreen;
|
||||
|
||||
[FieldOffset(0x8A)]
|
||||
public byte MultiplyBlue;
|
||||
|
||||
[FieldOffset(0x8B)]
|
||||
public byte MultiplyRed_2;
|
||||
|
||||
[FieldOffset(0x8C)]
|
||||
public byte MultiplyGreen_2;
|
||||
|
||||
[FieldOffset(0x8D)]
|
||||
public byte MultiplyBlue_2;
|
||||
|
||||
[FieldOffset(0x8E)]
|
||||
public byte Alpha_2;
|
||||
|
||||
[FieldOffset(0x8F)]
|
||||
public byte UnkByte_1;
|
||||
|
||||
[FieldOffset(0x90)]
|
||||
public ushort Width;
|
||||
|
||||
[FieldOffset(0x92)]
|
||||
public ushort Height;
|
||||
|
||||
[FieldOffset(0x94)]
|
||||
public float OriginX;
|
||||
|
||||
[FieldOffset(0x98)]
|
||||
public float OriginY;
|
||||
|
||||
[FieldOffset(0x9C)]
|
||||
public ushort Priority;
|
||||
|
||||
[FieldOffset(0x9E)]
|
||||
public short Flags;
|
||||
|
||||
[FieldOffset(0xA0)]
|
||||
public uint Flags_2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,455 +1,129 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui.Structs {
|
||||
#region Raw structs
|
||||
|
||||
internal static class PartyFinder {
|
||||
public static class PacketInfo {
|
||||
public static readonly int PacketSize = Marshal.SizeOf<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)]
|
||||
public readonly struct Packet {
|
||||
public readonly int batchNumber;
|
||||
internal readonly struct Packet
|
||||
{
|
||||
internal readonly int BatchNumber;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] padding1;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public readonly Listing[] listings;
|
||||
internal readonly Listing[] Listings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The structure of an individual listing within a packet.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct Listing {
|
||||
internal readonly struct Listing
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header1;
|
||||
|
||||
internal readonly uint id;
|
||||
internal readonly uint Id;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header2;
|
||||
|
||||
internal readonly uint contentIdLower;
|
||||
internal readonly uint ContentIdLower;
|
||||
private readonly ushort unknownShort1;
|
||||
private readonly ushort unknownShort2;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
private readonly byte[] header3;
|
||||
|
||||
internal readonly byte category;
|
||||
internal readonly byte Category;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header4;
|
||||
|
||||
internal readonly ushort duty;
|
||||
internal readonly byte dutyType;
|
||||
internal readonly ushort Duty;
|
||||
internal readonly byte DutyType;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
||||
private readonly byte[] header5;
|
||||
|
||||
internal readonly ushort world;
|
||||
internal readonly ushort World;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] header6;
|
||||
|
||||
internal readonly byte objective;
|
||||
internal readonly byte beginnersWelcome;
|
||||
internal readonly byte conditions;
|
||||
internal readonly byte dutyFinderSettings;
|
||||
internal readonly byte lootRules;
|
||||
internal readonly byte Objective;
|
||||
internal readonly byte BeginnersWelcome;
|
||||
internal readonly byte Conditions;
|
||||
internal readonly byte DutyFinderSettings;
|
||||
internal readonly byte LootRules;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header7; // all zero in every pf I've examined
|
||||
|
||||
private readonly uint lastPatchHotfixTimestamp; // last time the servers were restarted?
|
||||
internal readonly ushort secondsRemaining;
|
||||
internal readonly ushort SecondsRemaining;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
|
||||
|
||||
internal readonly ushort minimumItemLevel;
|
||||
internal readonly ushort homeWorld;
|
||||
internal readonly ushort currentWorld;
|
||||
internal readonly ushort MinimumItemLevel;
|
||||
internal readonly ushort HomeWorld;
|
||||
internal readonly ushort CurrentWorld;
|
||||
|
||||
private readonly byte header9;
|
||||
|
||||
internal readonly byte numSlots;
|
||||
internal readonly byte NumSlots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header10;
|
||||
|
||||
internal readonly byte searchArea;
|
||||
internal readonly byte SearchArea;
|
||||
|
||||
private readonly byte header11;
|
||||
|
||||
internal readonly byte numParties;
|
||||
internal readonly byte NumParties;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly uint[] slots;
|
||||
internal readonly uint[] Slots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly byte[] jobsPresent;
|
||||
internal readonly byte[] JobsPresent;
|
||||
|
||||
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
internal readonly byte[] name;
|
||||
internal readonly byte[] Name;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
|
||||
internal readonly byte[] description;
|
||||
internal readonly byte[] Description;
|
||||
|
||||
internal bool IsNull() {
|
||||
internal bool IsNull()
|
||||
{
|
||||
// a valid party finder must have at least one slot set
|
||||
return this.slots.All(slot => slot == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read-only classes
|
||||
|
||||
public class PartyFinderListing {
|
||||
/// <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();
|
||||
return this.Slots.All(slot => slot == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A player slot in a Party Finder listing.
|
||||
/// PartyFinder packet constants.
|
||||
/// </summary>
|
||||
public class PartyFinderSlot {
|
||||
private readonly uint accepting;
|
||||
private JobFlags[] listAccepting;
|
||||
|
||||
public static class PacketInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// List of jobs that this slot is accepting.
|
||||
/// The size of the PartyFinder packet.
|
||||
/// </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;
|
||||
public static readonly int PacketSize = Marshal.SizeOf<Packet>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
internal PartyFinderSlot(uint accepting) {
|
||||
this.accepting = accepting;
|
||||
}
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -25,12 +28,5 @@
|
|||
/// This only works if <see cref="IconId"/> is non-zero or <see cref="DisplayCheckmark"/> is true.
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen centre.
|
||||
/// </summary>
|
||||
Centre = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen right.
|
||||
/// </summary>
|
||||
Right = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen left.
|
||||
/// </summary>
|
||||
Left = 2,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
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
|
||||
{
|
||||
/// <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
|
||||
{
|
||||
/// <summary>
|
||||
/// The toast will be towards the bottom.
|
||||
/// </summary>
|
||||
Bottom = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The toast will be towards the top.
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -8,18 +8,80 @@ using Dalamud.Hooking;
|
|||
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
public sealed class ToastGui : IDisposable
|
||||
/// <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);
|
||||
|
||||
/// <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);
|
||||
|
||||
/// <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);
|
||||
|
||||
#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>
|
||||
/// Event that will be fired when a toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
|
|
@ -37,48 +99,9 @@ namespace Dalamud.Game.Internal.Gui
|
|||
|
||||
#endregion
|
||||
|
||||
#region Hooks
|
||||
|
||||
private readonly Hook<ShowNormalToastDelegate> showNormalToastHook;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables this module.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.showNormalToastHook.Enable();
|
||||
|
|
@ -86,6 +109,9 @@ namespace Dalamud.Game.Internal.Gui
|
|||
this.showErrorToastHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.showNormalToastHook.Dispose();
|
||||
|
|
@ -93,6 +119,30 @@ namespace Dalamud.Game.Internal.Gui
|
|||
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)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
|
|
@ -107,7 +157,7 @@ namespace Dalamud.Game.Internal.Gui
|
|||
var bytes = new List<byte>();
|
||||
unsafe
|
||||
{
|
||||
var ptr = (byte*) text;
|
||||
var ptr = (byte*)text;
|
||||
while (*ptr != 0)
|
||||
{
|
||||
bytes.Add(*ptr);
|
||||
|
|
@ -116,62 +166,42 @@ namespace Dalamud.Game.Internal.Gui
|
|||
}
|
||||
|
||||
// call events
|
||||
return this.Dalamud.SeStringManager.Parse(bytes.ToArray());
|
||||
return this.dalamud.SeStringManager.Parse(bytes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the toast queue.
|
||||
/// Handles normal toasts.
|
||||
/// </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>
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown</param>
|
||||
/// <param name="options">Options for the toast</param>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(string message, ToastOptions options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
this.NormalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown</param>
|
||||
/// <param name="options">Options for the toast</param>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(SeString message, ToastOptions options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
this.NormalQueue.Enqueue((message.Encode(), options));
|
||||
this.normalQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowNormal(byte[] bytes, ToastOptions options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
|
||||
var manager = this.Dalamud.Framework.Gui.GetUIModule();
|
||||
var manager = this.dalamud.Framework.Gui.GetUIModule();
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
|
@ -180,104 +210,11 @@ namespace Dalamud.Game.Internal.Gui
|
|||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleNormalToastDetour(manager, (IntPtr) ptr, 5, (byte) options.Position, (byte) options.Speed, 0);
|
||||
this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Quest API
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
|
|
@ -290,8 +227,8 @@ namespace Dalamud.Game.Internal.Gui
|
|||
var str = this.ParseString(text);
|
||||
var options = new ToastOptions
|
||||
{
|
||||
Position = (ToastPosition) isTop,
|
||||
Speed = (ToastSpeed) isFast,
|
||||
Position = (ToastPosition)isTop,
|
||||
Speed = (ToastSpeed)isFast,
|
||||
};
|
||||
|
||||
this.OnToast?.Invoke(ref str, ref options, ref isHandled);
|
||||
|
|
@ -308,7 +245,62 @@ namespace Dalamud.Game.Internal.Gui
|
|||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showNormalToastHook.Original(manager, (IntPtr) message, layer, (byte) options.Position, (byte) options.Speed, logMessageId);
|
||||
return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 options = new QuestToastOptions
|
||||
{
|
||||
Position = (QuestToastPosition) position,
|
||||
Position = (QuestToastPosition)position,
|
||||
DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic,
|
||||
IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1,
|
||||
PlaySound = playSound == 1,
|
||||
|
|
@ -341,7 +333,7 @@ namespace Dalamud.Game.Internal.Gui
|
|||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
var (ioc1, ioc2) = options.DetermineParameterOrder();
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
{
|
||||
|
|
@ -349,16 +341,63 @@ namespace Dalamud.Game.Internal.Gui
|
|||
{
|
||||
return this.showQuestToastHook.Original(
|
||||
manager,
|
||||
(int) options.Position,
|
||||
(IntPtr) message,
|
||||
(int)options.Position,
|
||||
(IntPtr)message,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte) 1 : (byte) 0,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options)
|
||||
{
|
||||
return options.DisplayCheckmark
|
||||
? (QuestToastCheckmarkMagic, options.IconId)
|
||||
: (options.IconId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
|
|
@ -384,7 +423,7 @@ namespace Dalamud.Game.Internal.Gui
|
|||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showErrorToastHook.Original(manager, (IntPtr) message, respectsHidingMaybe);
|
||||
return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,28 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui
|
||||
{
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
public class ToastGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowNormalToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowNormalToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowQuestToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowQuestToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ShowErrorToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowErrorToast { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
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 ?? ?? ?? ?? ??");
|
||||
|
|
|
|||
|
|
@ -1,32 +1,46 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
public sealed class LibcFunction {
|
||||
namespace Dalamud.Game.Internal.Libc
|
||||
{
|
||||
/// <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
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)]byte[] content, IntPtr size);
|
||||
private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)] byte[] content, IntPtr size);
|
||||
|
||||
// TODO: prolly callconv is not okay in x86
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr StdStringDeallocateDelegate(IntPtr address);
|
||||
|
||||
private LibcFunctionAddressResolver Address { get; }
|
||||
|
||||
private readonly StdStringFromCStringDelegate stdStringCtorCString;
|
||||
private readonly StdStringDeallocateDelegate stdStringDeallocate;
|
||||
|
||||
public LibcFunction(SigScanner scanner) {
|
||||
Address = new LibcFunctionAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
|
||||
this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(Address.StdStringFromCstring);
|
||||
this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(Address.StdStringDeallocate);
|
||||
}
|
||||
|
||||
public OwnedStdString NewString(byte[] content) {
|
||||
/// <summary>
|
||||
/// Create a new string from the given bytes.
|
||||
/// </summary>
|
||||
/// <param name="content">The bytes to convert.</param>
|
||||
/// <returns>An owned std string object.</returns>
|
||||
public OwnedStdString NewString(byte[] content)
|
||||
{
|
||||
// While 0x70 bytes in the memory should be enough in DX11 version,
|
||||
// I don't trust my analysis so we're just going to allocate almost two times more than that.
|
||||
var pString = Marshal.AllocHGlobal(256);
|
||||
|
|
@ -35,11 +49,17 @@ namespace Dalamud.Game.Internal.Libc {
|
|||
var size = new IntPtr(content.Length);
|
||||
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)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
|
@ -47,7 +67,8 @@ namespace Dalamud.Game.Internal.Libc {
|
|||
return this.NewString(encoding.GetBytes(content));
|
||||
}
|
||||
|
||||
private void DeallocateStdString(IntPtr address) {
|
||||
private void DeallocateStdString(IntPtr address)
|
||||
{
|
||||
this.stdStringDeallocate(address);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,29 @@
|
|||
using System;
|
||||
using System.Security.Policy;
|
||||
using System;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
public sealed class LibcFunctionAddressResolver : BaseAddressResolver {
|
||||
namespace Dalamud.Game.Internal.Libc
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="LibcFunction"/> class.
|
||||
/// </summary>
|
||||
public sealed class LibcFunctionAddressResolver : BaseAddressResolver
|
||||
{
|
||||
private delegate IntPtr StringFromCString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native StdStringFromCstring method.
|
||||
/// </summary>
|
||||
public IntPtr StdStringFromCstring { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native StdStringDeallocate method.
|
||||
/// </summary>
|
||||
public IntPtr StdStringDeallocate { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
|
||||
StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
|
||||
this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
public sealed class OwnedStdString : IDisposable {
|
||||
internal delegate void DeallocatorDelegate(IntPtr address);
|
||||
|
||||
// ala. the drop flag
|
||||
private bool isDisposed;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc
|
||||
{
|
||||
/// <summary>
|
||||
/// An address wrapper around the <see cref="StdString"/> class.
|
||||
/// </summary>
|
||||
public sealed partial class OwnedStdString
|
||||
{
|
||||
private readonly DeallocatorDelegate dealloc;
|
||||
|
||||
public IntPtr Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a wrapper around std::string
|
||||
/// Initializes a new instance of the <see cref="OwnedStdString"/> class.
|
||||
/// Construct a wrapper around std::string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Violating any of these might cause an undefined hehaviour.
|
||||
|
|
@ -22,47 +20,82 @@ namespace Dalamud.Game.Internal.Libc {
|
|||
/// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address.
|
||||
/// 3. std::string object pointed by address must be initialized before calling this function.
|
||||
/// </remarks>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="address">The address of the owned std string.</param>
|
||||
/// <param name="dealloc">A deallocator function.</param>
|
||||
/// <returns></returns>
|
||||
internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) {
|
||||
Address = address;
|
||||
internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc)
|
||||
{
|
||||
this.Address = address;
|
||||
this.dealloc = dealloc;
|
||||
}
|
||||
|
||||
~OwnedStdString() {
|
||||
ReleaseUnmanagedResources();
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources() {
|
||||
if (Address == IntPtr.Zero) {
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
throw new AccessViolationException();
|
||||
}
|
||||
|
||||
// Deallocate inner string first
|
||||
this.dealloc(Address);
|
||||
this.dealloc(this.Address);
|
||||
|
||||
// Free the heap
|
||||
Marshal.FreeHGlobal(Address);
|
||||
Marshal.FreeHGlobal(this.Address);
|
||||
|
||||
// Better safe (running on a nullptr) than sorry. (running on a dangling pointer)
|
||||
Address = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// No double free plz, kthx.
|
||||
if (this.isDisposed) {
|
||||
return;
|
||||
}
|
||||
this.isDisposed = true;
|
||||
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public StdString Read() {
|
||||
return StdString.ReadFromPointer(Address);
|
||||
this.Address = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,56 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
namespace Dalamud.Game.Internal.Libc
|
||||
{
|
||||
/// <summary>
|
||||
/// Interation with std::string
|
||||
/// Interation with std::string.
|
||||
/// </summary>
|
||||
public class StdString {
|
||||
public static StdString ReadFromPointer(IntPtr cstring) {
|
||||
unsafe {
|
||||
if (cstring == IntPtr.Zero) {
|
||||
public class StdString
|
||||
{
|
||||
/// <summary>
|
||||
/// 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));
|
||||
}
|
||||
|
||||
var innerAddress = Marshal.ReadIntPtr(cstring);
|
||||
if (innerAddress == IntPtr.Zero) {
|
||||
if (innerAddress == IntPtr.Zero)
|
||||
{
|
||||
throw new NullReferenceException("Inner reference to the cstring is null.");
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
|
||||
// Count the number of chars. String is assumed to be zero-terminated.
|
||||
while (Marshal.ReadByte(innerAddress + count) != 0) {
|
||||
|
||||
var count = 0;
|
||||
while (Marshal.ReadByte(innerAddress, count) != 0)
|
||||
{
|
||||
count += 1;
|
||||
}
|
||||
|
||||
|
|
@ -33,17 +58,12 @@ namespace Dalamud.Game.Internal.Libc {
|
|||
var rawData = new byte[count];
|
||||
Marshal.Copy(innerAddress, rawData, 0, count);
|
||||
|
||||
return new StdString {
|
||||
return new StdString
|
||||
{
|
||||
RawData = rawData,
|
||||
Value = Encoding.UTF8.GetString(rawData)
|
||||
Value = Encoding.UTF8.GetString(rawData),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private StdString() { }
|
||||
|
||||
public string Value { get; private set; }
|
||||
|
||||
public byte[] RawData { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,88 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
using SharpDX.DXGI;
|
||||
|
||||
namespace Dalamud.Game.Internal.Network {
|
||||
public sealed class GameNetwork : IDisposable {
|
||||
#region Hooks
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
|
||||
private readonly Hook<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>
|
||||
/// Event that is called when a network message is sent/received.
|
||||
/// </summary>
|
||||
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[]>();
|
||||
|
||||
public GameNetwork(SigScanner scanner) {
|
||||
Address = new GameNetworkAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameNetwork"/> class.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
public GameNetwork(SigScanner scanner)
|
||||
{
|
||||
this.address = new GameNetworkAddressResolver();
|
||||
this.address.Setup(scanner);
|
||||
|
||||
Log.Verbose("===== G A M E N E T W O R K =====");
|
||||
Log.Verbose("ProcessZonePacketDown address {ProcessZonePacketDown}", Address.ProcessZonePacketDown);
|
||||
Log.Verbose("ProcessZonePacketUp address {ProcessZonePacketUp}", Address.ProcessZonePacketUp);
|
||||
Log.Verbose("ProcessZonePacketDown address {ProcessZonePacketDown}", this.address.ProcessZonePacketDown);
|
||||
Log.Verbose("ProcessZonePacketUp address {ProcessZonePacketUp}", this.address.ProcessZonePacketUp);
|
||||
|
||||
this.processZonePacketDownHook =
|
||||
new Hook<ProcessZonePacketDownDelegate>(Address.ProcessZonePacketDown,
|
||||
new ProcessZonePacketDownDelegate(ProcessZonePacketDownDetour),
|
||||
this);
|
||||
this.processZonePacketDownHook = new Hook<ProcessZonePacketDownDelegate>(this.address.ProcessZonePacketDown, new ProcessZonePacketDownDelegate(this.ProcessZonePacketDownDetour), this);
|
||||
|
||||
this.processZonePacketUpHook =
|
||||
new Hook<ProcessZonePacketUpDelegate>(Address.ProcessZonePacketUp,
|
||||
new ProcessZonePacketUpDelegate(ProcessZonePacketUpDetour),
|
||||
this);
|
||||
this.processZonePacketUpHook = new Hook<ProcessZonePacketUpDelegate>(this.address.ProcessZonePacketUp, new ProcessZonePacketUpDelegate(this.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.processZonePacketUpHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.processZonePacketDownHook.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;
|
||||
|
||||
// Go back 0x10 to get back to the start of the packet header
|
||||
dataPtr -= 0x10;
|
||||
|
||||
try {
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Call events
|
||||
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort) Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
|
||||
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
|
||||
|
||||
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string header;
|
||||
try {
|
||||
try
|
||||
{
|
||||
var data = new byte[32];
|
||||
Marshal.Copy(dataPtr, data, 0, 32);
|
||||
header = BitConverter.ToString(data);
|
||||
} catch (Exception) {
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
header = "failed";
|
||||
}
|
||||
|
||||
|
|
@ -92,13 +132,13 @@ namespace Dalamud.Game.Internal.Network {
|
|||
}
|
||||
}
|
||||
|
||||
private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) {
|
||||
|
||||
private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Call events
|
||||
// TODO: Implement actor IDs
|
||||
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort) Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp);
|
||||
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -121,43 +161,26 @@ namespace Dalamud.Game.Internal.Network {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
public void InjectZoneProtoPacket(byte[] data) {
|
||||
private void InjectZoneProtoPacket(byte[] data)
|
||||
{
|
||||
this.zoneInjectQueue.Enqueue(data);
|
||||
}
|
||||
|
||||
private void InjectActorControl(short cat, int param1) {
|
||||
var packetData = new byte[] {
|
||||
private void InjectActorControl(short cat, int param1)
|
||||
{
|
||||
var packetData = new byte[]
|
||||
{
|
||||
0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00,
|
||||
0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00
|
||||
0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00,
|
||||
};
|
||||
|
||||
BitConverter.GetBytes((short) cat).CopyTo(packetData, 0x10);
|
||||
BitConverter.GetBytes((short)cat).CopyTo(packetData, 0x10);
|
||||
|
||||
BitConverter.GetBytes((UInt32) param1).CopyTo(packetData, 0x14);
|
||||
BitConverter.GetBytes((uint)param1).CopyTo(packetData, 0x14);
|
||||
|
||||
InjectZoneProtoPacket(packetData);
|
||||
this.InjectZoneProtoPacket(packetData);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <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;
|
||||
|
||||
namespace Dalamud.Game.Internal.Network {
|
||||
public sealed class GameNetworkAddressResolver : BaseAddressResolver {
|
||||
namespace Dalamud.Game.Internal.Network
|
||||
{
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ProcessZonePacketUp method.
|
||||
/// </summary>
|
||||
public IntPtr ProcessZonePacketUp { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2");
|
||||
ProcessZonePacketUp =
|
||||
sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2");
|
||||
this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
namespace Dalamud.Game.Internal.Network {
|
||||
public enum NetworkMessageDirection {
|
||||
namespace Dalamud.Game.Internal.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// This represents the direction of a network message.
|
||||
/// </summary>
|
||||
public enum NetworkMessageDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// A zone down message.
|
||||
/// </summary>
|
||||
ZoneDown,
|
||||
ZoneUp
|
||||
|
||||
/// <summary>
|
||||
/// A zone up message.
|
||||
/// </summary>
|
||||
ZoneUp,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,135 +1,148 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.File
|
||||
{
|
||||
public class ResourceManager {
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7);
|
||||
/// <summary>
|
||||
/// This class facilitates modifying how the game loads resources from disk.
|
||||
/// </summary>
|
||||
public class ResourceManager
|
||||
{
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly ResourceManagerAddressResolver address;
|
||||
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 ResourceManagerAddressResolver Address { get; }
|
||||
private readonly Dalamud dalamud;
|
||||
private Dictionary<IntPtr, ResourceHandleHookInfo> resourceHookMap = new();
|
||||
|
||||
class ResourceHandleHookInfo {
|
||||
public string Path { get; set; }
|
||||
public Stream DetourFile { get; set; }
|
||||
}
|
||||
|
||||
private Dictionary<IntPtr, ResourceHandleHookInfo> resourceHookMap = new Dictionary<IntPtr, ResourceHandleHookInfo>();
|
||||
|
||||
public ResourceManager(Dalamud dalamud, SigScanner scanner) {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dalamud">The Dalamud instance.</param>
|
||||
/// <param name="scanner">The SigScanner instance.</param>
|
||||
public ResourceManager(Dalamud dalamud, SigScanner scanner)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
Address = new ResourceManagerAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
this.address = new ResourceManagerAddressResolver();
|
||||
this.address.Setup(scanner);
|
||||
|
||||
Log.Verbose("===== R E S O U R C E M A N A G E R =====");
|
||||
Log.Verbose("GetResourceAsync address {GetResourceAsync}", Address.GetResourceAsync);
|
||||
Log.Verbose("GetResourceSync address {GetResourceSync}", Address.GetResourceSync);
|
||||
Log.Verbose("GetResourceAsync address {GetResourceAsync}", this.address.GetResourceAsync);
|
||||
Log.Verbose("GetResourceSync address {GetResourceSync}", this.address.GetResourceSync);
|
||||
|
||||
this.getResourceAsyncHook =
|
||||
new Hook<GetResourceAsyncDelegate>(Address.GetResourceAsync,
|
||||
new GetResourceAsyncDelegate(GetResourceAsyncDetour),
|
||||
this);
|
||||
|
||||
this.getResourceSyncHook =
|
||||
new Hook<GetResourceSyncDelegate>(Address.GetResourceSync,
|
||||
new GetResourceSyncDelegate(GetResourceSyncDetour),
|
||||
this);
|
||||
this.getResourceAsyncHook = new Hook<GetResourceAsyncDelegate>(this.address.GetResourceAsync, new GetResourceAsyncDelegate(this.GetResourceAsyncDetour), this);
|
||||
|
||||
this.getResourceSyncHook = new Hook<GetResourceSyncDelegate>(this.address.GetResourceSync, new GetResourceSyncDelegate(this.GetResourceSyncDetour), this);
|
||||
}
|
||||
|
||||
public void Enable() {
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6, byte a7);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetResourceSyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6);
|
||||
|
||||
/// <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.getResourceSyncHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.getResourceAsyncHook.Dispose();
|
||||
this.getResourceSyncHook.Dispose();
|
||||
}
|
||||
|
||||
private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7) {
|
||||
|
||||
try {
|
||||
var path = Marshal.PtrToStringAnsi(a5);
|
||||
private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6, byte a7)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = Marshal.PtrToStringAnsi(pathPtr);
|
||||
|
||||
var resourceHandle = this.getResourceAsyncHook.Original(manager, a2, a3, a4, IntPtr.Zero, a6, a7);
|
||||
//var resourceHandle = IntPtr.Zero;
|
||||
// var resourceHandle = IntPtr.Zero;
|
||||
|
||||
Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, a5, a6, a7, resourceHandle);
|
||||
Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, pathPtr, a6, a7, resourceHandle);
|
||||
|
||||
Log.Verbose($"->{path}");
|
||||
|
||||
HandleGetResourceHookAcquire(resourceHandle, path);
|
||||
this.HandleGetResourceHookAcquire(resourceHandle, path);
|
||||
|
||||
return resourceHandle;
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReadResourceAsync hook.");
|
||||
|
||||
return this.getResourceAsyncHook.Original(manager, a2, a3, a4, a5, a6, a7);
|
||||
return this.getResourceAsyncHook.Original(manager, a2, a3, a4, pathPtr, a6, a7);
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpMem(IntPtr address, int len = 512) {
|
||||
if (address == IntPtr.Zero)
|
||||
return;
|
||||
private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr pathPtr, IntPtr a6)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, pathPtr, a6);
|
||||
|
||||
var data = new byte[len];
|
||||
Marshal.Copy(address, data, 0, len);
|
||||
Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, pathPtr, a6, resourceHandle);
|
||||
|
||||
Log.Verbose($"MEMDMP at {address.ToInt64():X} for {len:X}\n{Util.ByteArrayToHex(data)}");
|
||||
}
|
||||
|
||||
private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6) {
|
||||
|
||||
try {
|
||||
var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
|
||||
|
||||
Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, a5, a6, resourceHandle);
|
||||
|
||||
var path = Marshal.PtrToStringAnsi(a5);
|
||||
var path = Marshal.PtrToStringAnsi(pathPtr);
|
||||
|
||||
Log.Verbose($"->{path}");
|
||||
|
||||
HandleGetResourceHookAcquire(resourceHandle, path);
|
||||
this.HandleGetResourceHookAcquire(resourceHandle, path);
|
||||
|
||||
return resourceHandle;
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReadResourceSync hook.");
|
||||
|
||||
return this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
|
||||
return this.getResourceSyncHook.Original(manager, a2, a3, a4, pathPtr, a6);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path) {
|
||||
private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path)
|
||||
{
|
||||
if (FilePathHasInvalidChars(path))
|
||||
return;
|
||||
|
||||
if (this.resourceHookMap.ContainsKey(handlePtr)) {
|
||||
if (this.resourceHookMap.ContainsKey(handlePtr))
|
||||
{
|
||||
Log.Verbose($"-> Handle {handlePtr.ToInt64():X}({path}) was cached!");
|
||||
return;
|
||||
}
|
||||
|
||||
var hookInfo = new ResourceHandleHookInfo {
|
||||
Path = path
|
||||
var hookInfo = new ResourceHandleHookInfo
|
||||
{
|
||||
Path = path,
|
||||
};
|
||||
|
||||
var hookPath = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "ResourceHook", path);
|
||||
|
||||
if (System.IO.File.Exists(hookPath)) {
|
||||
if (System.IO.File.Exists(hookPath))
|
||||
{
|
||||
hookInfo.DetourFile = new FileStream(hookPath, FileMode.Open);
|
||||
Log.Verbose("-> Added resource hook detour at {0}", hookPath);
|
||||
}
|
||||
|
|
@ -137,10 +150,11 @@ namespace Dalamud.Game.Internal.File
|
|||
this.resourceHookMap.Add(handlePtr, hookInfo);
|
||||
}
|
||||
|
||||
public static bool FilePathHasInvalidChars(string path)
|
||||
private class ResourceHandleHookInfo
|
||||
{
|
||||
public string Path { get; set; }
|
||||
|
||||
return (!string.IsNullOrEmpty(path) && path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0);
|
||||
public Stream DetourFile { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.Game.Internal.File
|
||||
{
|
||||
class ResourceManagerAddressResolver : BaseAddressResolver
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the GetResourceSync method.
|
||||
/// </summary>
|
||||
public IntPtr GetResourceSync { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74");
|
||||
GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6");
|
||||
//ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74");
|
||||
this.GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6");
|
||||
// ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.Network.Structures;
|
||||
|
||||
namespace Dalamud.Game.Network {
|
||||
internal class MarketBoardItemRequest {
|
||||
namespace Dalamud.Game.Network
|
||||
{
|
||||
internal class MarketBoardItemRequest
|
||||
{
|
||||
public uint CatalogId { get; set; }
|
||||
|
||||
public byte AmountToArrive { get; set; }
|
||||
|
||||
public List<MarketBoardCurrentOfferings.MarketBoardItemListing> Listings { get; set; }
|
||||
|
||||
public List<MarketBoardHistory.MarketBoardHistoryListing> History { get; set; }
|
||||
|
||||
public int ListingsRequestId { get; set; } = -1;
|
||||
|
||||
public bool IsDone => Listings.Count == AmountToArrive && History.Count != 0;
|
||||
public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
using Dalamud.Game.Network.Structures;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders {
|
||||
internal interface IMarketBoardUploader {
|
||||
void Upload(MarketBoardItemRequest itemRequest);
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,57 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisHistoryEntry {
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||
{
|
||||
/// <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")]
|
||||
public bool Hq { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item price per unit.
|
||||
/// </summary>
|
||||
[JsonProperty("pricePerUnit")]
|
||||
public uint PricePerUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the quantity of items available.
|
||||
/// </summary>
|
||||
[JsonProperty("quantity")]
|
||||
public uint Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the buyer.
|
||||
/// </summary>
|
||||
[JsonProperty("buyerName")]
|
||||
public string BuyerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this item was on a mannequin.
|
||||
/// </summary>
|
||||
[JsonProperty("onMannequin")]
|
||||
public bool OnMannequin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the seller ID.
|
||||
/// </summary>
|
||||
[JsonProperty("sellerID")]
|
||||
public string SellerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the buyer ID.
|
||||
/// </summary>
|
||||
[JsonProperty("buyerID")]
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp of the transaction.
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")]
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisHistoryUploadRequest {
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||
{
|
||||
/// <summary>
|
||||
/// A Universalis API structure.
|
||||
/// </summary>
|
||||
internal class UniversalisHistoryUploadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the world ID.
|
||||
/// </summary>
|
||||
[JsonProperty("worldID")]
|
||||
public uint WorldId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item ID.
|
||||
/// </summary>
|
||||
[JsonProperty("itemID")]
|
||||
public uint ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of available entries.
|
||||
/// </summary>
|
||||
[JsonProperty("entries")]
|
||||
public List<UniversalisHistoryEntry> Entries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the uploader ID.
|
||||
/// </summary>
|
||||
[JsonProperty("uploaderID")]
|
||||
public string UploaderId { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,95 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisItemListingsEntry {
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||
{
|
||||
/// <summary>
|
||||
/// A Universalis API structure.
|
||||
/// </summary>
|
||||
internal class UniversalisItemListingsEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the listing ID.
|
||||
/// </summary>
|
||||
[JsonProperty("listingID")]
|
||||
public string ListingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the item is HQ.
|
||||
/// </summary>
|
||||
[JsonProperty("hq")]
|
||||
public bool Hq { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item price per unit.
|
||||
/// </summary>
|
||||
[JsonProperty("pricePerUnit")]
|
||||
public uint PricePerUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item quantity.
|
||||
/// </summary>
|
||||
[JsonProperty("quantity")]
|
||||
public uint Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the retainer selling the item.
|
||||
/// </summary>
|
||||
[JsonProperty("retainerName")]
|
||||
public string RetainerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the retainer selling the item.
|
||||
/// </summary>
|
||||
[JsonProperty("retainerID")]
|
||||
public string RetainerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the user who created the entry.
|
||||
/// </summary>
|
||||
[JsonProperty("creatorName")]
|
||||
public string CreatorName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the item is on a mannequin.
|
||||
/// </summary>
|
||||
[JsonProperty("onMannequin")]
|
||||
public bool OnMannequin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the seller ID.
|
||||
/// </summary>
|
||||
[JsonProperty("sellerID")]
|
||||
public string SellerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the user who created the entry.
|
||||
/// </summary>
|
||||
[JsonProperty("creatorID")]
|
||||
public string CreatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the dye on the item.
|
||||
/// </summary>
|
||||
[JsonProperty("stainID")]
|
||||
public int StainId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the city where the selling retainer resides.
|
||||
/// </summary>
|
||||
[JsonProperty("retainerCity")]
|
||||
public int RetainerCity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last time the entry was reviewed.
|
||||
/// </summary>
|
||||
[JsonProperty("lastReviewTime")]
|
||||
public long LastReviewTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the materia attached to the item.
|
||||
/// </summary>
|
||||
[JsonProperty("materia")]
|
||||
public List<UniversalisItemMateria> Materia { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisItemListingsUploadRequest {
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||
{
|
||||
/// <summary>
|
||||
/// A Universalis API structure.
|
||||
/// </summary>
|
||||
internal class UniversalisItemListingsUploadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the world ID.
|
||||
/// </summary>
|
||||
[JsonProperty("worldID")]
|
||||
public uint WorldId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item ID.
|
||||
/// </summary>
|
||||
[JsonProperty("itemID")]
|
||||
public uint ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of available items.
|
||||
/// </summary>
|
||||
[JsonProperty("listings")]
|
||||
public List<UniversalisItemListingsEntry> Listings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the uploader ID.
|
||||
/// </summary>
|
||||
[JsonProperty("uploaderID")]
|
||||
public string UploaderId { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisItemMateria {
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
||||
{
|
||||
/// <summary>
|
||||
/// A Universalis API structure.
|
||||
/// </summary>
|
||||
internal class UniversalisItemMateria
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the item slot ID.
|
||||
/// </summary>
|
||||
[JsonProperty("slotID")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the materia ID.
|
||||
/// </summary>
|
||||
[JsonProperty("materiaID")]
|
||||
public int MateriaId { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
||||
using Dalamud.Game.Network.MarketBoardUploaders.Universalis;
|
||||
using Dalamud.Game.Network.Structures;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
||||
internal class UniversalisMarketBoardUploader : IMarketBoardUploader {
|
||||
namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders
|
||||
{
|
||||
/// <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://127.0.0.1:443";
|
||||
// private const string ApiBase = "https://127.0.0.1:443";
|
||||
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
public UniversalisMarketBoardUploader(Dalamud dalamud) {
|
||||
/// <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;
|
||||
}
|
||||
|
||||
public void Upload(MarketBoardItemRequest request) {
|
||||
using (var client = new WebClient()) {
|
||||
/// <inheritdoc/>
|
||||
public void Upload(MarketBoardItemRequest request)
|
||||
{
|
||||
using var client = new WebClient();
|
||||
|
||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||
|
||||
Log.Verbose("Starting Universalis upload.");
|
||||
|
|
@ -33,8 +47,10 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
|||
listingsRequestObject.ItemId = request.CatalogId;
|
||||
|
||||
listingsRequestObject.Listings = new List<UniversalisItemListingsEntry>();
|
||||
foreach (var marketBoardItemListing in request.Listings) {
|
||||
var universalisListing = new UniversalisItemListingsEntry {
|
||||
foreach (var marketBoardItemListing in request.Listings)
|
||||
{
|
||||
var universalisListing = new UniversalisItemListingsEntry
|
||||
{
|
||||
Hq = marketBoardItemListing.IsHq,
|
||||
SellerId = marketBoardItemListing.RetainerOwnerId.ToString(),
|
||||
RetainerName = marketBoardItemListing.RetainerName,
|
||||
|
|
@ -42,18 +58,21 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
|||
CreatorId = marketBoardItemListing.ArtisanId.ToString(),
|
||||
CreatorName = marketBoardItemListing.PlayerName,
|
||||
OnMannequin = marketBoardItemListing.OnMannequin,
|
||||
LastReviewTime = ((DateTimeOffset) marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
|
||||
LastReviewTime = ((DateTimeOffset)marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
|
||||
PricePerUnit = marketBoardItemListing.PricePerUnit,
|
||||
Quantity = marketBoardItemListing.ItemQuantity,
|
||||
RetainerCity = marketBoardItemListing.RetainerCityId
|
||||
RetainerCity = marketBoardItemListing.RetainerCityId,
|
||||
};
|
||||
|
||||
universalisListing.Materia = new List<UniversalisItemMateria>();
|
||||
foreach (var itemMateria in marketBoardItemListing.Materia)
|
||||
universalisListing.Materia.Add(new UniversalisItemMateria {
|
||||
{
|
||||
universalisListing.Materia.Add(new UniversalisItemMateria
|
||||
{
|
||||
MateriaId = itemMateria.MateriaId,
|
||||
SlotId = itemMateria.Index
|
||||
SlotId = itemMateria.Index,
|
||||
});
|
||||
}
|
||||
|
||||
listingsRequestObject.Listings.Add(universalisListing);
|
||||
}
|
||||
|
|
@ -69,14 +88,17 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
|||
|
||||
historyRequestObject.Entries = new List<UniversalisHistoryEntry>();
|
||||
foreach (var marketBoardHistoryListing in request.History)
|
||||
historyRequestObject.Entries.Add(new UniversalisHistoryEntry {
|
||||
{
|
||||
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()
|
||||
Timestamp = ((DateTimeOffset)marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds(),
|
||||
});
|
||||
}
|
||||
|
||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||
|
||||
|
|
@ -86,22 +108,24 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
|||
|
||||
Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId);
|
||||
}
|
||||
}
|
||||
|
||||
public void UploadTax(MarketTaxRates taxRates) {
|
||||
using (var client = new WebClient())
|
||||
/// <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 {
|
||||
taxRatesRequest.TaxData = new UniversalisTaxData
|
||||
{
|
||||
LimsaLominsa = taxRates.LimsaLominsaTax,
|
||||
Gridania = taxRates.GridaniaTax,
|
||||
Uldah = taxRates.UldahTax,
|
||||
Ishgard = taxRates.IshgardTax,
|
||||
Kugane = taxRates.KuganeTax,
|
||||
Crystarium = taxRates.CrystariumTax
|
||||
Crystarium = taxRates.CrystariumTax,
|
||||
};
|
||||
|
||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||
|
|
@ -113,5 +137,4 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
|||
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;
|
||||
|
||||
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")]
|
||||
public string UploaderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the world to retrieve data from.
|
||||
/// </summary>
|
||||
[JsonProperty("worldID")]
|
||||
public uint WorldId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets tax data for each city's market.
|
||||
/// </summary>
|
||||
[JsonProperty("marketTaxRates")]
|
||||
public UniversalisTaxData TaxData { get; set; }
|
||||
}
|
||||
|
||||
class UniversalisTaxData {
|
||||
[JsonProperty("limsaLominsa")]
|
||||
public uint LimsaLominsa { get; set; }
|
||||
|
||||
[JsonProperty("gridania")]
|
||||
public uint Gridania { get; set; }
|
||||
|
||||
[JsonProperty("uldah")]
|
||||
public uint Uldah { get; set; }
|
||||
|
||||
[JsonProperty("ishgard")]
|
||||
public uint Ishgard { get; set; }
|
||||
|
||||
[JsonProperty("kugane")]
|
||||
public uint Kugane { get; set; }
|
||||
|
||||
[JsonProperty("crystarium")]
|
||||
public uint Crystarium { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,47 +4,58 @@ using System.Diagnostics;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Internal.Network;
|
||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
||||
using Dalamud.Game.Network.Structures;
|
||||
using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Network {
|
||||
public class NetworkHandlers {
|
||||
namespace Dalamud.Game.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles network notifications and uploading Marketboard data.
|
||||
/// </summary>
|
||||
public class NetworkHandlers
|
||||
{
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private readonly List<MarketBoardItemRequest> marketBoardRequests = new List<MarketBoardItemRequest>();
|
||||
private readonly List<MarketBoardItemRequest> marketBoardRequests = new();
|
||||
|
||||
private readonly bool optOutMbUploads;
|
||||
private readonly IMarketBoardUploader uploader;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NetworkHandlers"/> class.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
this.optOutMbUploads = optOutMbUploads;
|
||||
|
||||
this.uploader = new UniversalisMarketBoardUploader(dalamud);
|
||||
|
||||
dalamud.Framework.Network.OnNetworkMessage += this.OnNetworkMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event which gets fired when a duty is ready.
|
||||
/// </summary>
|
||||
public event EventHandler<ContentFinderCondition> CfPop;
|
||||
|
||||
public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads) {
|
||||
this.dalamud = dalamud;
|
||||
this.optOutMbUploads = optOutMbUploads;
|
||||
|
||||
this.uploader = new UniversalisMarketBoardUploader(dalamud);
|
||||
|
||||
dalamud.Framework.Network.OnNetworkMessage += OnNetworkMessage;
|
||||
|
||||
}
|
||||
|
||||
private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) {
|
||||
private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction)
|
||||
{
|
||||
if (direction != NetworkMessageDirection.ZoneDown)
|
||||
return;
|
||||
|
||||
if (!this.dalamud.Data.IsDataReady)
|
||||
return;
|
||||
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) {
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"])
|
||||
{
|
||||
var data = new byte[64];
|
||||
Marshal.Copy(dataPtr, data, 0, 64);
|
||||
|
||||
|
|
@ -63,98 +74,114 @@ namespace Dalamud.Game.Network {
|
|||
}
|
||||
|
||||
var cfcName = contentFinderCondition.Name.ToString();
|
||||
if (string.IsNullOrEmpty(contentFinderCondition.Name)) {
|
||||
if (string.IsNullOrEmpty(contentFinderCondition.Name))
|
||||
{
|
||||
cfcName = "Duty Roulette";
|
||||
contentFinderCondition.Image = 112324;
|
||||
}
|
||||
|
||||
if (this.dalamud.Configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) {
|
||||
var flashInfo = new NativeFunctions.FLASHWINFO
|
||||
if (this.dalamud.Configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated())
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<NativeFunctions.FLASHWINFO>(),
|
||||
var flashInfo = new NativeFunctions.FlashWindowInfo
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<NativeFunctions.FlashWindowInfo>(),
|
||||
uCount = uint.MaxValue,
|
||||
dwTimeout = 0,
|
||||
dwFlags = NativeFunctions.FlashWindow.FLASHW_ALL |
|
||||
NativeFunctions.FlashWindow.FLASHW_TIMERNOFG,
|
||||
hwnd = Process.GetCurrentProcess().MainWindowHandle
|
||||
dwFlags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG,
|
||||
hwnd = Process.GetCurrentProcess().MainWindowHandle,
|
||||
};
|
||||
NativeFunctions.FlashWindowEx(ref flashInfo);
|
||||
}
|
||||
|
||||
Task.Run(() => {
|
||||
if(this.dalamud.Configuration.DutyFinderChatMessage)
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (this.dalamud.Configuration.DutyFinderChatMessage)
|
||||
this.dalamud.Framework.Gui.Chat.Print("Duty pop: " + cfcName);
|
||||
|
||||
CfPop?.Invoke(this, contentFinderCondition);
|
||||
this.CfPop?.Invoke(this, contentFinderCondition);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.optOutMbUploads) {
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"]) {
|
||||
var catalogId = (uint) Marshal.ReadInt32(dataPtr);
|
||||
if (!this.optOutMbUploads)
|
||||
{
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"])
|
||||
{
|
||||
var catalogId = (uint)Marshal.ReadInt32(dataPtr);
|
||||
var amount = Marshal.ReadByte(dataPtr + 0xB);
|
||||
|
||||
this.marketBoardRequests.Add(new MarketBoardItemRequest {
|
||||
this.marketBoardRequests.Add(new MarketBoardItemRequest
|
||||
{
|
||||
CatalogId = catalogId,
|
||||
AmountToArrive = amount,
|
||||
Listings = new List<MarketBoardCurrentOfferings.MarketBoardItemListing>(),
|
||||
History = new List<MarketBoardHistory.MarketBoardHistoryListing>()
|
||||
History = new List<MarketBoardHistory.MarketBoardHistoryListing>(),
|
||||
});
|
||||
|
||||
Log.Verbose($"NEW MB REQUEST START: item#{catalogId} amount#{amount}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"]) {
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"])
|
||||
{
|
||||
var listing = MarketBoardCurrentOfferings.Read(dataPtr);
|
||||
|
||||
var request =
|
||||
this.marketBoardRequests.LastOrDefault(
|
||||
r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
|
||||
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
|
||||
|
||||
if (request == null) {
|
||||
Log.Error(
|
||||
$"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
|
||||
if (request == null)
|
||||
{
|
||||
Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) {
|
||||
Log.Error(
|
||||
$"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
|
||||
if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive)
|
||||
{
|
||||
Log.Error($"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) {
|
||||
Log.Error(
|
||||
$"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
|
||||
if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId)
|
||||
{
|
||||
Log.Error($"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId == -1 && request.Listings.Count > 0) {
|
||||
Log.Error(
|
||||
$"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
||||
if (request.ListingsRequestId == -1 && request.Listings.Count > 0)
|
||||
{
|
||||
Log.Error($"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId == -1) {
|
||||
if (request.ListingsRequestId == -1)
|
||||
{
|
||||
request.ListingsRequestId = listing.RequestId;
|
||||
Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}");
|
||||
}
|
||||
|
||||
request.Listings.AddRange(listing.ItemListings);
|
||||
|
||||
Log.Verbose("Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
|
||||
listing.ItemListings.Count, request.ListingsRequestId, request.Listings.Count,
|
||||
request.AmountToArrive, request.CatalogId);
|
||||
Log.Verbose(
|
||||
"Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
|
||||
listing.ItemListings.Count,
|
||||
request.ListingsRequestId,
|
||||
request.Listings.Count,
|
||||
request.AmountToArrive,
|
||||
request.CatalogId);
|
||||
|
||||
if (request.IsDone) {
|
||||
Log.Verbose("Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
|
||||
request.ListingsRequestId, request.CatalogId, request.AmountToArrive);
|
||||
try {
|
||||
if (request.IsDone)
|
||||
{
|
||||
Log.Verbose(
|
||||
"Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
|
||||
request.ListingsRequestId,
|
||||
request.CatalogId,
|
||||
request.AmountToArrive);
|
||||
try
|
||||
{
|
||||
Task.Run(() => this.uploader.Upload(request));
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Market Board data upload failed.");
|
||||
}
|
||||
}
|
||||
|
|
@ -162,18 +189,21 @@ namespace Dalamud.Game.Network {
|
|||
return;
|
||||
}
|
||||
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"]) {
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"])
|
||||
{
|
||||
var listing = MarketBoardHistory.Read(dataPtr);
|
||||
|
||||
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId);
|
||||
|
||||
if (request == null) {
|
||||
if (request == null)
|
||||
{
|
||||
Log.Error(
|
||||
$"Market Board data arrived without a corresponding request: item#{listing.CatalogId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId != -1) {
|
||||
if (request.ListingsRequestId != -1)
|
||||
{
|
||||
Log.Error(
|
||||
$"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
||||
return;
|
||||
|
|
@ -183,7 +213,8 @@ namespace Dalamud.Game.Network {
|
|||
|
||||
Log.Verbose("Added history for item#{0}", listing.CatalogId);
|
||||
|
||||
if (request.AmountToArrive == 0) {
|
||||
if (request.AmountToArrive == 0)
|
||||
{
|
||||
Log.Verbose("Request had 0 amount, uploading now");
|
||||
|
||||
try
|
||||
|
|
@ -197,17 +228,25 @@ namespace Dalamud.Game.Network {
|
|||
}
|
||||
}
|
||||
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"]) {
|
||||
var category = (uint) Marshal.ReadInt32(dataPtr);
|
||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"])
|
||||
{
|
||||
var category = (uint)Marshal.ReadInt32(dataPtr);
|
||||
// Result dialog packet does not contain market tax rates
|
||||
if (category != 720905) {
|
||||
if (category != 720905)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var taxes = MarketTaxRates.Read(dataPtr);
|
||||
|
||||
Log.Verbose("MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
|
||||
taxes.LimsaLominsaTax, taxes.GridaniaTax, taxes.UldahTax, taxes.IshgardTax, taxes.KuganeTax, taxes.CrystariumTax);
|
||||
Log.Verbose(
|
||||
"MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
|
||||
taxes.LimsaLominsaTax,
|
||||
taxes.GridaniaTax,
|
||||
taxes.UldahTax,
|
||||
taxes.IshgardTax,
|
||||
taxes.KuganeTax,
|
||||
taxes.CrystariumTax);
|
||||
try
|
||||
{
|
||||
Task.Run(() => this.uploader.UploadTax(taxes));
|
||||
|
|
|
|||
|
|
@ -17,10 +17,9 @@ namespace Dalamud.Game.Network.Structures
|
|||
{
|
||||
var output = new MarketBoardCurrentOfferings();
|
||||
|
||||
using (var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544))
|
||||
{
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
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++)
|
||||
|
|
@ -77,8 +76,6 @@ namespace Dalamud.Game.Network.Structures
|
|||
output.ListingIndexEnd = reader.ReadByte();
|
||||
output.ListingIndexStart = reader.ReadByte();
|
||||
output.RequestId = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,30 +3,36 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Game.Network.Structures {
|
||||
public class MarketBoardHistory {
|
||||
namespace Dalamud.Game.Network.Structures
|
||||
{
|
||||
public class MarketBoardHistory
|
||||
{
|
||||
public uint CatalogId;
|
||||
public uint CatalogId2;
|
||||
|
||||
public List<MarketBoardHistoryListing> HistoryListings;
|
||||
|
||||
public static unsafe MarketBoardHistory Read(IntPtr dataPtr) {
|
||||
public static unsafe MarketBoardHistory Read(IntPtr dataPtr)
|
||||
{
|
||||
var output = new MarketBoardHistory();
|
||||
|
||||
using (var stream = new UnmanagedMemoryStream((byte*) dataPtr.ToPointer(), 1544)) {
|
||||
using (var reader = new BinaryReader(stream)) {
|
||||
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
output.CatalogId = reader.ReadUInt32();
|
||||
output.CatalogId2 = reader.ReadUInt32();
|
||||
|
||||
output.HistoryListings = new List<MarketBoardHistoryListing>();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var listingEntry = new MarketBoardHistoryListing();
|
||||
|
||||
listingEntry.SalePrice = reader.ReadUInt32();
|
||||
listingEntry.PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime;
|
||||
listingEntry.Quantity = reader.ReadUInt32();
|
||||
listingEntry.IsHq = reader.ReadBoolean();
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var listingEntry = new MarketBoardHistoryListing
|
||||
{
|
||||
SalePrice = reader.ReadUInt32(),
|
||||
PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime,
|
||||
Quantity = reader.ReadUInt32(),
|
||||
IsHq = reader.ReadBoolean(),
|
||||
};
|
||||
|
||||
reader.ReadBoolean();
|
||||
|
||||
|
|
@ -37,13 +43,12 @@ namespace Dalamud.Game.Network.Structures {
|
|||
if (listingEntry.CatalogId != 0)
|
||||
output.HistoryListings.Add(listingEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public class MarketBoardHistoryListing {
|
||||
public class MarketBoardHistoryListing
|
||||
{
|
||||
public string BuyerName;
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
|
||||
namespace Dalamud.Game
|
||||
{
|
||||
/// <summary>
|
||||
/// This class enables TCP optimizations in the game socket for better performance.
|
||||
/// </summary>
|
||||
internal sealed class WinSockHandlers : IDisposable
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||
private delegate IntPtr SocketDelegate(int af, int type, int protocol);
|
||||
private Hook<SocketDelegate> ws2SocketHook;
|
||||
|
||||
[DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||
private static extern int setsockopt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen);
|
||||
|
||||
public WinSockHandlers() {
|
||||
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", new SocketDelegate(OnSocket));
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WinSockHandlers"/> class.
|
||||
/// </summary>
|
||||
public WinSockHandlers()
|
||||
{
|
||||
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", new SocketDelegate(this.OnSocket));
|
||||
this.ws2SocketHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||
private delegate IntPtr SocketDelegate(int af, int type, int protocol);
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.ws2SocketHook.Dispose();
|
||||
}
|
||||
|
||||
private IntPtr OnSocket(int af, int type, int protocol)
|
||||
{
|
||||
var socket = this.ws2SocketHook.Original(af, type, protocol);
|
||||
|
|
@ -37,19 +47,15 @@ namespace Dalamud.Game
|
|||
// https://linux.die.net/man/7/tcp
|
||||
// https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf
|
||||
var value = new IntPtr(1);
|
||||
setsockopt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4);
|
||||
NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4);
|
||||
|
||||
// Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards.
|
||||
value = new IntPtr(1);
|
||||
setsockopt(socket, SocketOptionLevel.Tcp, (SocketOptionName)12, ref value, 4);
|
||||
NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.AddMembership, ref value, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
ws2SocketHook.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace Dalamud.Game
|
|||
/// <summary>
|
||||
/// Gets the base address of the .text section search area.
|
||||
/// </summary>
|
||||
public IntPtr TextSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.TextSectionOffset);
|
||||
public IntPtr TextSectionBase => new(this.SearchBase.ToInt64() + this.TextSectionOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset of the .text section from the base of the module.
|
||||
|
|
@ -71,7 +71,7 @@ namespace Dalamud.Game
|
|||
/// <summary>
|
||||
/// Gets the base address of the .data section search area.
|
||||
/// </summary>
|
||||
public IntPtr DataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.DataSectionOffset);
|
||||
public IntPtr DataSectionBase => new(this.SearchBase.ToInt64() + this.DataSectionOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset of the .data section from the base of the module.
|
||||
|
|
@ -86,7 +86,7 @@ namespace Dalamud.Game
|
|||
/// <summary>
|
||||
/// Gets the base address of the .rdata section search area.
|
||||
/// </summary>
|
||||
public IntPtr RDataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.RDataSectionOffset);
|
||||
public IntPtr RDataSectionBase => new(this.SearchBase.ToInt64() + this.RDataSectionOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset of the .rdata section from the base of the module.
|
||||
|
|
@ -230,7 +230,7 @@ namespace Dalamud.Game
|
|||
return IntPtr.Add(sigLocation, 5 + jumpOffset);
|
||||
}
|
||||
|
||||
private static (byte[] needle, bool[] mask) ParseSignature(string signature)
|
||||
private static (byte[] Needle, bool[] Mask) ParseSignature(string signature)
|
||||
{
|
||||
signature = signature.Replace(" ", string.Empty);
|
||||
if (signature.Length % 2 != 0)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ namespace Dalamud.Game.Text.Sanitizer
|
|||
/// </summary>
|
||||
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
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> FRSanitizationDict = new Dictionary<string, string>
|
||||
private static readonly Dictionary<string, string> FRSanitizationDict = new()
|
||||
{
|
||||
{ "\u0153", "\u006F\u0065" }, // ligature oe
|
||||
};
|
||||
|
|
@ -75,18 +75,13 @@ namespace Dalamud.Game.Text.Sanitizer
|
|||
private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage)
|
||||
{
|
||||
var sanitizedString = FilterUnprintableCharacters(unsanitizedString);
|
||||
switch (clientLanguage)
|
||||
return clientLanguage switch
|
||||
{
|
||||
case ClientLanguage.Japanese:
|
||||
case ClientLanguage.English:
|
||||
return sanitizedString;
|
||||
case ClientLanguage.German:
|
||||
return FilterByDict(sanitizedString, DESanitizationDict);
|
||||
case ClientLanguage.French:
|
||||
return FilterByDict(sanitizedString, FRSanitizationDict);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null);
|
||||
}
|
||||
ClientLanguage.Japanese or ClientLanguage.English => sanitizedString,
|
||||
ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict),
|
||||
ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null),
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<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
|
||||
{
|
||||
/// <summary>
|
||||
/// Special unicode characters with game-related symbols that work both in-game and in any dalamud window.
|
||||
/// </summary>
|
||||
public enum SeIconChar {
|
||||
public enum SeIconChar
|
||||
{
|
||||
/// <summary>
|
||||
/// The sprout icon unicode character.
|
||||
/// </summary>
|
||||
BotanistSprout = 0xE034,
|
||||
|
||||
/// <summary>
|
||||
/// The item level icon unicode character.
|
||||
/// </summary>
|
||||
ItemLevel = 0xE033,
|
||||
|
||||
/// <summary>
|
||||
/// The auto translate open icon unicode character.
|
||||
/// </summary>
|
||||
AutoTranslateOpen = 0xE040,
|
||||
|
||||
/// <summary>
|
||||
/// The auto translate close icon unicode character.
|
||||
/// </summary>
|
||||
AutoTranslateClose = 0xE041,
|
||||
|
||||
/// <summary>
|
||||
/// The high quality icon unicode character.
|
||||
/// </summary>
|
||||
HighQuality = 0xE03C,
|
||||
|
||||
/// <summary>
|
||||
/// The clock icon unicode character.
|
||||
/// </summary>
|
||||
Clock = 0xE031,
|
||||
|
||||
/// <summary>
|
||||
/// The gil icon unicode character.
|
||||
/// </summary>
|
||||
Gil = 0xE049,
|
||||
|
||||
/// <summary>
|
||||
/// The Hydaelyn icon unicode character.
|
||||
/// </summary>
|
||||
Hyadelyn = 0xE048,
|
||||
|
||||
/// <summary>
|
||||
/// The no mouse click icon unicode character.
|
||||
/// </summary>
|
||||
MouseNoClick = 0xE050,
|
||||
|
||||
/// <summary>
|
||||
/// The left mouse click icon unicode character.
|
||||
/// </summary>
|
||||
MouseLeftClick = 0xE051,
|
||||
|
||||
/// <summary>
|
||||
/// The right mouse click icon unicode character.
|
||||
/// </summary>
|
||||
MouseRightClick = 0xE052,
|
||||
|
||||
/// <summary>
|
||||
/// The left/right mouse click icon unicode character.
|
||||
/// </summary>
|
||||
MouseBothClick = 0xE053,
|
||||
|
||||
/// <summary>
|
||||
/// The mouse wheel icon unicode character.
|
||||
/// </summary>
|
||||
MouseWheel = 0xE054,
|
||||
|
||||
/// <summary>
|
||||
/// The mouse with a 1 icon unicode character.
|
||||
/// </summary>
|
||||
Mouse1 = 0xE055,
|
||||
|
||||
/// <summary>
|
||||
/// The mouse with a 2 icon unicode character.
|
||||
/// </summary>
|
||||
Mouse2 = 0xE056,
|
||||
|
||||
/// <summary>
|
||||
/// The mouse with a 3 icon unicode character.
|
||||
/// </summary>
|
||||
Mouse3 = 0xE057,
|
||||
|
||||
/// <summary>
|
||||
/// The mouse with a 4 icon unicode character.
|
||||
/// </summary>
|
||||
Mouse4 = 0xE058,
|
||||
|
||||
/// <summary>
|
||||
/// The mouse with a 5 icon unicode character.
|
||||
/// </summary>
|
||||
Mouse5 = 0xE059,
|
||||
|
||||
/// <summary>
|
||||
/// The level English icon unicode character.
|
||||
/// </summary>
|
||||
LevelEn = 0xE06A,
|
||||
|
||||
/// <summary>
|
||||
/// The level German icon unicode character.
|
||||
/// </summary>
|
||||
LevelDe = 0xE06B,
|
||||
|
||||
/// <summary>
|
||||
/// The level French icon unicode character.
|
||||
/// </summary>
|
||||
LevelFr = 0xE06C,
|
||||
|
||||
/// <summary>
|
||||
/// The experience icon unicode character.
|
||||
/// </summary>
|
||||
Experience = 0xE0BC,
|
||||
|
||||
/// <summary>
|
||||
/// The experience filled icon unicode character.
|
||||
/// </summary>
|
||||
ExperienceFilled = 0xE0BD,
|
||||
|
||||
/// <summary>
|
||||
/// The A.M. time icon unicode character.
|
||||
/// </summary>
|
||||
TimeAm = 0xE06D,
|
||||
|
||||
/// <summary>
|
||||
/// The P.M. time icon unicode character.
|
||||
/// </summary>
|
||||
TimePm = 0xE06E,
|
||||
|
||||
/// <summary>
|
||||
/// The right arrow icon unicode character.
|
||||
/// </summary>
|
||||
ArrowRight = 0xE06F,
|
||||
|
||||
/// <summary>
|
||||
/// The down arrow icon unicode character.
|
||||
/// </summary>
|
||||
ArrowDown = 0xE035,
|
||||
|
||||
/// <summary>
|
||||
/// The number 0 icon unicode character.
|
||||
/// </summary>
|
||||
Number0 = 0xE060,
|
||||
|
||||
/// <summary>
|
||||
/// The number 1 icon unicode character.
|
||||
/// </summary>
|
||||
Number1 = 0xE061,
|
||||
|
||||
/// <summary>
|
||||
/// The number 2 icon unicode character.
|
||||
/// </summary>
|
||||
Number2 = 0xE062,
|
||||
|
||||
/// <summary>
|
||||
/// The number 3 icon unicode character.
|
||||
/// </summary>
|
||||
Number3 = 0xE063,
|
||||
|
||||
/// <summary>
|
||||
/// The number 4 icon unicode character.
|
||||
/// </summary>
|
||||
Number4 = 0xE064,
|
||||
|
||||
/// <summary>
|
||||
/// The number 5 icon unicode character.
|
||||
/// </summary>
|
||||
Number5 = 0xE065,
|
||||
|
||||
/// <summary>
|
||||
/// The number 6 icon unicode character.
|
||||
/// </summary>
|
||||
Number6 = 0xE066,
|
||||
|
||||
/// <summary>
|
||||
/// The number 7 icon unicode character.
|
||||
/// </summary>
|
||||
Number7 = 0xE067,
|
||||
|
||||
/// <summary>
|
||||
/// The number 8 icon unicode character.
|
||||
/// </summary>
|
||||
Number8 = 0xE068,
|
||||
|
||||
/// <summary>
|
||||
/// The number 9 icon unicode character.
|
||||
/// </summary>
|
||||
Number9 = 0xE069,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 0 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber0 = 0xE08F,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 1 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber1 = 0xE090,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 2 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber2 = 0xE091,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 3 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber3 = 0xE092,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 4 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber4 = 0xE093,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 5 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber5 = 0xE094,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 6 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber6 = 0xE095,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 7 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber7 = 0xE096,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 8 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber8 = 0xE097,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 9 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber9 = 0xE098,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 10 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber10 = 0xE099,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 11 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber11 = 0xE09A,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 12 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber12 = 0xE09B,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 13 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber13 = 0xE09C,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 14 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber14 = 0xE09D,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 15 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber15 = 0xE09E,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 16 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber16 = 0xE09F,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 17 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber17 = 0xE0A0,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 18 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber18 = 0xE0A1,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 19 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber19 = 0xE0A2,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 20 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber20 = 0xE0A3,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 21 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber21 = 0xE0A4,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 22 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber22 = 0xE0A5,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 23 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber23 = 0xE0A6,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 24 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber24 = 0xE0A7,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 25 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber25 = 0xE0A8,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 26 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber26 = 0xE0A9,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 27 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber27 = 0xE0AA,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 28 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber28 = 0xE0AB,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 29 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber29 = 0xE0AC,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 30 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber30 = 0xE0AD,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed number 31 icon unicode character.
|
||||
/// </summary>
|
||||
BoxedNumber31 = 0xE0AE,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed plus icon unicode character.
|
||||
/// </summary>
|
||||
BoxedPlus = 0xE0AF,
|
||||
|
||||
/// <summary>
|
||||
/// The bosed question mark icon unicode character.
|
||||
/// </summary>
|
||||
BoxedQuestionMark = 0xE070,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed star icon unicode character.
|
||||
/// </summary>
|
||||
BoxedStar = 0xE0C0,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed Roman numeral 1 (I) icon unicode character.
|
||||
/// </summary>
|
||||
BoxedRoman1 = 0xE0C1,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed Roman numeral 2 (II) icon unicode character.
|
||||
/// </summary>
|
||||
BoxedRoman2 = 0xE0C2,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed Roman numeral 3 (III) icon unicode character.
|
||||
/// </summary>
|
||||
BoxedRoman3 = 0xE0C3,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed Roman numeral 4 (IV) icon unicode character.
|
||||
/// </summary>
|
||||
BoxedRoman4 = 0xE0C4,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed Roman numeral 5 (V) icon unicode character.
|
||||
/// </summary>
|
||||
BoxedRoman5 = 0xE0C5,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed Roman numeral 6 (VI) icon unicode character.
|
||||
/// </summary>
|
||||
BoxedRoman6 = 0xE0C6,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter A icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterA = 0xE071,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter B icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterB = 0xE072,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter C icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterC = 0xE073,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter D icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterD = 0xE074,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter E icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterE = 0xE075,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter F icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterF = 0xE076,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter G icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterG = 0xE077,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter H icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterH = 0xE078,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter I icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterI = 0xE079,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter J icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterJ = 0xE07A,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter K icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterK = 0xE07B,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter L icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterL = 0xE07C,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter M icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterM = 0xE07D,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter N icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterN = 0xE07E,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter O icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterO = 0xE07F,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter P icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterP = 0xE080,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter Q icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterQ = 0xE081,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter R icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterR = 0xE082,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter S icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterS = 0xE083,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter T icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterT = 0xE084,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter U icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterU = 0xE085,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter V icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterV = 0xE086,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter W icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterW = 0xE087,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter X icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterX = 0xE088,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter Y icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterY = 0xE089,
|
||||
|
||||
/// <summary>
|
||||
/// The boxed letter Z icon unicode character.
|
||||
/// </summary>
|
||||
BoxedLetterZ = 0xE08A,
|
||||
|
||||
/// <summary>
|
||||
/// The circle icon unicode character.
|
||||
/// </summary>
|
||||
Circle = 0xE04A,
|
||||
|
||||
/// <summary>
|
||||
/// The square icon unicode character.
|
||||
/// </summary>
|
||||
Square = 0xE04B,
|
||||
|
||||
/// <summary>
|
||||
/// The cross icon unicode character.
|
||||
/// </summary>
|
||||
Cross = 0xE04C,
|
||||
|
||||
/// <summary>
|
||||
/// The triangle icon unicode character.
|
||||
/// </summary>
|
||||
Triangle = 0xE04D,
|
||||
|
||||
/// <summary>
|
||||
/// The hexagon icon unicode character.
|
||||
/// </summary>
|
||||
Hexagon = 0xE042,
|
||||
|
||||
/// <summary>
|
||||
/// The no-circle/prohobited icon unicode character.
|
||||
/// </summary>
|
||||
Prohibited = 0xE043,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The dice icon unicode character.
|
||||
/// </summary>
|
||||
Dice = 0xE03E,
|
||||
|
||||
/// <summary>
|
||||
/// The debuff icon unicode character.
|
||||
/// </summary>
|
||||
Debuff = 0xE05B,
|
||||
|
||||
/// <summary>
|
||||
/// The buff icon unicode character.
|
||||
/// </summary>
|
||||
Buff = 0xE05C,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world icon unicode character.
|
||||
/// </summary>
|
||||
CrossWorld = 0xE05D,
|
||||
|
||||
/// <summary>
|
||||
/// The Eureka level icon unicode character.
|
||||
/// </summary>
|
||||
EurekaLevel = 0xE03A,
|
||||
|
||||
/// <summary>
|
||||
/// The link marker icon unicode character.
|
||||
/// </summary>
|
||||
LinkMarker = 0xE0BB,
|
||||
|
||||
/// <summary>
|
||||
/// The glamoured icon unicode character.
|
||||
/// </summary>
|
||||
Glamoured = 0xE03B,
|
||||
|
||||
/// <summary>
|
||||
/// The glamoured and dyed icon unicode character.
|
||||
/// </summary>
|
||||
GlamouredDyed = 0xE04E,
|
||||
|
||||
/// <summary>
|
||||
/// The synced quest icon unicode character.
|
||||
/// </summary>
|
||||
QuestSync = 0xE0BE,
|
||||
|
||||
/// <summary>
|
||||
/// The repeatable quest icon unicode character.
|
||||
/// </summary>
|
||||
QuestRepeatable = 0xE0BF,
|
||||
|
||||
/// <summary>
|
||||
/// The IME hiragana icon unicode character.
|
||||
/// </summary>
|
||||
ImeHiragana = 0xE020,
|
||||
|
||||
/// <summary>
|
||||
/// The IME katakana icon unicode character.
|
||||
/// </summary>
|
||||
ImeKatakana = 0xE021,
|
||||
|
||||
/// <summary>
|
||||
/// The IME alphanumeric icon unicode character.
|
||||
/// </summary>
|
||||
ImeAlphanumeric = 0xE022,
|
||||
|
||||
/// <summary>
|
||||
/// The IME katakana half-width icon unicode character.
|
||||
/// </summary>
|
||||
ImeKatakanaHalfWidth = 0xE023,
|
||||
|
||||
/// <summary>
|
||||
/// The IME alphanumeric half-width icon unicode character.
|
||||
/// </summary>
|
||||
ImeAlphanumericHalfWidth = 0xE024,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (1) icon unicode character.
|
||||
/// </summary>
|
||||
Instance1 = 0xE0B1,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (2) icon unicode character.
|
||||
/// </summary>
|
||||
Instance2 = 0xE0B2,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (3) icon unicode character.
|
||||
/// </summary>
|
||||
Instance3 = 0xE0B3,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (4) icon unicode character.
|
||||
/// </summary>
|
||||
Instance4 = 0xE0B4,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (5) icon unicode character.
|
||||
/// </summary>
|
||||
Instance5 = 0xE0B5,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (6) icon unicode character.
|
||||
/// </summary>
|
||||
Instance6 = 0xE0B6,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (7) icon unicode character.
|
||||
/// </summary>
|
||||
Instance7 = 0xE0B7,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (8) icon unicode character.
|
||||
/// </summary>
|
||||
Instance8 = 0xE0B8,
|
||||
|
||||
/// <summary>
|
||||
/// The instance (9) icon unicode character.
|
||||
/// </summary>
|
||||
Instance9 = 0xE0B9,
|
||||
|
||||
/// <summary>
|
||||
/// The instance merged icon unicode character.
|
||||
/// </summary>
|
||||
InstanceMerged = 0xE0BA,
|
||||
|
||||
/// <summary>
|
||||
/// The English local time icon unicode character.
|
||||
/// </summary>
|
||||
LocalTimeEn = 0xE0D0,
|
||||
|
||||
/// <summary>
|
||||
/// The English server time icon unicode character.
|
||||
/// </summary>
|
||||
ServerTimeEn = 0xE0D1,
|
||||
|
||||
/// <summary>
|
||||
/// The English Eorzea time icon unicode character.
|
||||
/// </summary>
|
||||
EorzeaTimeEn = 0xE0D2,
|
||||
|
||||
/// <summary>
|
||||
/// The German local time icon unicode character.
|
||||
/// </summary>
|
||||
LocalTimeDe = 0xE0D3,
|
||||
|
||||
/// <summary>
|
||||
/// The German server time icon unicode character.
|
||||
/// </summary>
|
||||
ServerTimeDe = 0xE0D4,
|
||||
|
||||
/// <summary>
|
||||
/// The German Eorzea time icon unicode character.
|
||||
/// </summary>
|
||||
EorzeaTimeDe = 0xE0D5,
|
||||
|
||||
/// <summary>
|
||||
/// The French local time icon unicode character.
|
||||
/// </summary>
|
||||
LocalTimeFr = 0xE0D6,
|
||||
|
||||
/// <summary>
|
||||
/// The French server time icon unicode character.
|
||||
/// </summary>
|
||||
ServerTimeFr = 0xE0D7,
|
||||
|
||||
/// <summary>
|
||||
/// The French Eorzea time icon unicode character.
|
||||
/// </summary>
|
||||
EorzeaTimeFr = 0xE0D8,
|
||||
|
||||
/// <summary>
|
||||
/// The Japanese local time icon unicode character.
|
||||
/// </summary>
|
||||
LocalTimeJa = 0xE0D9,
|
||||
|
||||
/// <summary>
|
||||
/// The Japanese server time icon unicode character.
|
||||
/// </summary>
|
||||
ServerTimeJa = 0xE0DA,
|
||||
|
||||
/// <summary>
|
||||
/// The Japanese Eorzea time icon unicode character.
|
||||
/// </summary>
|
||||
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 {
|
||||
public enum BitmapFontIcon : uint {
|
||||
None,
|
||||
ControllerDPadUp,
|
||||
ControllerDPadDown,
|
||||
ControllerDPadLeft,
|
||||
ControllerDPadRight,
|
||||
ControllerDPadUpDown,
|
||||
ControllerDPadLeftRight,
|
||||
ControllerDPadAll,
|
||||
/// <summary>
|
||||
/// The controller D-pad up icon.
|
||||
/// </summary>
|
||||
ControllerDPadUp = 1,
|
||||
|
||||
ControllerButton0, // Xbox B / PS Circle
|
||||
ControllerButton1, // Xbox A / PS Cross
|
||||
ControllerButton2, // Xbox X / PS Square
|
||||
ControllerButton3, // Xbox Y / PS Triangle
|
||||
/// <summary>
|
||||
/// The controller D-pad down icon.
|
||||
/// </summary>
|
||||
ControllerDPadDown = 2,
|
||||
|
||||
ControllerShoulderLeft,
|
||||
ControllerShoulderRight,
|
||||
/// <summary>
|
||||
/// The controller D-pad left icon.
|
||||
/// </summary>
|
||||
ControllerDPadLeft = 3,
|
||||
|
||||
ControllerTriggerLeft,
|
||||
ControllerTriggerRight,
|
||||
/// <summary>
|
||||
/// The controller D-pad right icon.
|
||||
/// </summary>
|
||||
ControllerDPadRight = 4,
|
||||
|
||||
ControllerAnalogLeftStickIn,
|
||||
ControllerAnalogRightStickIn,
|
||||
/// <summary>
|
||||
/// The controller D-pad up/down icon.
|
||||
/// </summary>
|
||||
ControllerDPadUpDown = 5,
|
||||
|
||||
ControllerStart,
|
||||
ControllerBack,
|
||||
/// <summary>
|
||||
/// The controller D-pad left/right icon.
|
||||
/// </summary>
|
||||
ControllerDPadLeftRight = 6,
|
||||
|
||||
ControllerAnalogLeftStick,
|
||||
ControllerAnalogLeftStickUpDown,
|
||||
ControllerAnalogLeftStickLeftRight,
|
||||
/// <summary>
|
||||
/// The controller D-pad all directions icon.
|
||||
/// </summary>
|
||||
ControllerDPadAll = 7,
|
||||
|
||||
ControllerAnalogRightStick,
|
||||
ControllerAnalogRightStickUpDown,
|
||||
ControllerAnalogRightStickLeftRight,
|
||||
/// <summary>
|
||||
/// The controller button 0 icon (Xbox: B, PlayStation: Circle).
|
||||
/// </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,
|
||||
BlackShroud,
|
||||
Thanalan,
|
||||
AutoTranslateBegin,
|
||||
AutoTranslateEnd,
|
||||
ElementFire,
|
||||
ElementIce,
|
||||
ElementWind,
|
||||
ElementEarth,
|
||||
ElementLightning,
|
||||
ElementWater,
|
||||
LevelSync,
|
||||
Warning,
|
||||
Ishgard,
|
||||
Aetheryte,
|
||||
Aethernet,
|
||||
|
||||
GoldStar,
|
||||
SilverStar,
|
||||
/// <summary>
|
||||
/// 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,
|
||||
SwordUnsheathed,
|
||||
SwordSheathed,
|
||||
|
||||
Dice,
|
||||
/// <summary>
|
||||
/// The unsheathed sword icon.
|
||||
/// </summary>
|
||||
SwordUnsheathed = 71,
|
||||
|
||||
FlyZone,
|
||||
FlyZoneLocked,
|
||||
/// <summary>
|
||||
/// The sheathed sword icon.
|
||||
/// </summary>
|
||||
SwordSheathed = 72,
|
||||
|
||||
NoCircle,
|
||||
/// <summary>
|
||||
/// The dice icon.
|
||||
/// </summary>
|
||||
Dice = 73,
|
||||
|
||||
NewAdventurer,
|
||||
Mentor,
|
||||
MentorPvE,
|
||||
MentorCrafting,
|
||||
MentorPvP,
|
||||
/// <summary>
|
||||
/// The flyable zone icon.
|
||||
/// </summary>
|
||||
FlyZone = 74,
|
||||
|
||||
Tank,
|
||||
Healer,
|
||||
DPS,
|
||||
Crafter,
|
||||
Gatherer,
|
||||
AnyClass,
|
||||
/// <summary>
|
||||
/// The no-flying zone icon.
|
||||
/// </summary>
|
||||
FlyZoneLocked = 75,
|
||||
|
||||
CrossWorld,
|
||||
/// <summary>
|
||||
/// The no-circle/prohibited icon.
|
||||
/// </summary>
|
||||
NoCircle = 76,
|
||||
|
||||
FateSlay,
|
||||
FateBoss,
|
||||
FateGather,
|
||||
FateDefend,
|
||||
FateEscort,
|
||||
FateSpecial1,
|
||||
/// <summary>
|
||||
/// The sprout icon.
|
||||
/// </summary>
|
||||
NewAdventurer = 77,
|
||||
|
||||
Returner,
|
||||
/// <summary>
|
||||
/// The mentor icon.
|
||||
/// </summary>
|
||||
Mentor = 78,
|
||||
|
||||
FarEast,
|
||||
GyrAbania,
|
||||
/// <summary>
|
||||
/// 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,
|
||||
ExclamationRectangle,
|
||||
/// <summary>
|
||||
/// The tank role icon.
|
||||
/// </summary>
|
||||
Tank = 82,
|
||||
|
||||
NotoriousMonster,
|
||||
/// <summary>
|
||||
/// The healer role icon.
|
||||
/// </summary>
|
||||
Healer = 83,
|
||||
|
||||
Recording,
|
||||
Alarm,
|
||||
/// <summary>
|
||||
/// The DPS role icon.
|
||||
/// </summary>
|
||||
DPS = 84,
|
||||
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
Crystarium,
|
||||
/// <summary>
|
||||
/// The crafter role icon.
|
||||
/// </summary>
|
||||
Crafter = 85,
|
||||
|
||||
MentorProblem,
|
||||
/// <summary>
|
||||
/// The gatherer role icon.
|
||||
/// </summary>
|
||||
Gatherer = 86,
|
||||
|
||||
FateUnknownGold,
|
||||
/// <summary>
|
||||
/// The "any" role icon.
|
||||
/// </summary>
|
||||
AnyClass = 87,
|
||||
|
||||
OrangeDiamond,
|
||||
FateCrafting
|
||||
/// <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
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface binding for a payload that can provide readable Text.
|
||||
/// </summary>
|
||||
public interface ITextProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the readable text.
|
||||
/// </summary>
|
||||
string Text { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Serilog;
|
||||
|
|
@ -19,35 +19,8 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
/// <summary>
|
||||
/// This class represents a parsed SeString payload.
|
||||
/// </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>
|
||||
/// The Lumina instance to use for any necessary data lookups.
|
||||
/// </summary>
|
||||
|
|
@ -58,31 +31,26 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
private byte[] encodedData;
|
||||
|
||||
/// <summary>
|
||||
/// Encode this payload object into a byte[] useable in-game for things like the chat log.
|
||||
/// Gets the type of this payload.
|
||||
/// </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 (Dirty || force)
|
||||
{
|
||||
this.encodedData = EncodeImpl();
|
||||
Dirty = false;
|
||||
}
|
||||
public abstract PayloadType Type { get; }
|
||||
|
||||
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>
|
||||
/// Decodes a binary representation of a payload into its corresponding nice object payload.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public static Payload Decode(BinaryReader reader, DataManager data)
|
||||
{
|
||||
var payloadStartPos = reader.BaseStream.Position;
|
||||
|
||||
Payload payload = null;
|
||||
Payload payload;
|
||||
|
||||
var initialByte = reader.ReadByte();
|
||||
reader.BaseStream.Position--;
|
||||
|
|
@ -113,6 +81,39 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
return payload;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
Payload payload = null;
|
||||
|
|
@ -171,11 +172,13 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
{
|
||||
Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType);
|
||||
}
|
||||
|
||||
// rewind so we capture the Interactable byte in the raw data
|
||||
reader.BaseStream.Seek(-1, SeekOrigin.Current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SeStringChunkType.AutoTranslateKey:
|
||||
|
|
@ -216,43 +219,126 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
#region parse constants and helpers
|
||||
|
||||
/// <summary>
|
||||
/// Parsing helpers.
|
||||
/// </summary>
|
||||
public abstract partial class Payload
|
||||
{
|
||||
/// <summary>
|
||||
/// The start byte of a payload.
|
||||
/// </summary>
|
||||
protected const byte START_BYTE = 0x02;
|
||||
|
||||
/// <summary>
|
||||
/// The end byte of a payload.
|
||||
/// </summary>
|
||||
protected const byte END_BYTE = 0x03;
|
||||
|
||||
protected enum SeStringChunkType
|
||||
{
|
||||
Icon = 0x12,
|
||||
EmphasisItalic = 0x1A,
|
||||
SeHyphen = 0x1F,
|
||||
Interactable = 0x27,
|
||||
AutoTranslateKey = 0x2E,
|
||||
UIForeground = 0x48,
|
||||
UIGlow = 0x49
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This represents the type of embedded info in a payload.
|
||||
/// </summary>
|
||||
public enum EmbeddedInfoType
|
||||
{
|
||||
/// <summary>
|
||||
/// A player's name.
|
||||
/// </summary>
|
||||
PlayerName = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// The link to an iteme.
|
||||
/// </summary>
|
||||
ItemLink = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// The link to a map position.
|
||||
/// </summary>
|
||||
MapPositionLink = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// The link to a quest.
|
||||
/// </summary>
|
||||
QuestLink = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// A status effect.
|
||||
/// </summary>
|
||||
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
|
||||
// in which case it should probably go live somewhere else
|
||||
protected static uint GetInteger(BinaryReader input)
|
||||
{
|
||||
uint marker = input.ReadByte();
|
||||
if (marker < 0xD0) return marker - 1;
|
||||
if (marker < 0xD0)
|
||||
return marker - 1;
|
||||
|
||||
// the game adds 0xF0 marker for values >= 0xCF
|
||||
// uasge of 0xD0-0xEF is unknown, should we throw here?
|
||||
|
|
@ -269,6 +355,11 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
return BitConverter.ToUInt32(ret, 0);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (value < 0xCF)
|
||||
|
|
@ -287,22 +378,33 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
ret[0] |= (byte)(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
ret[0] -= 1;
|
||||
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
protected static (uint, uint) GetPackedIntegers(BinaryReader input)
|
||||
/// <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);
|
||||
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);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
namespace Dalamud.Game.Text.SeStringHandling
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -10,54 +9,70 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
/// An SeString payload representing a player link.
|
||||
/// </summary>
|
||||
Player,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing an Item link.
|
||||
/// </summary>
|
||||
Item,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing an Status Effect link.
|
||||
/// </summary>
|
||||
Status,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing raw, typed text.
|
||||
/// </summary>
|
||||
RawText,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a text foreground color.
|
||||
/// </summary>
|
||||
UIForeground,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a text glow color.
|
||||
/// </summary>
|
||||
UIGlow,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a map position link, such as from <flag> or <pos>.
|
||||
/// </summary>
|
||||
MapLink,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing an auto-translate dictionary entry.
|
||||
/// </summary>
|
||||
AutoTranslateText,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing italic emphasis formatting on text.
|
||||
/// </summary>
|
||||
EmphasisItalic,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a bitmap icon.
|
||||
/// </summary>
|
||||
Icon,
|
||||
|
||||
/// <summary>
|
||||
/// A SeString payload representing a quest link.
|
||||
/// </summary>
|
||||
Quest,
|
||||
|
||||
/// <summary>
|
||||
/// A SeString payload representing a custom clickable link for dalamud plugins
|
||||
/// A SeString payload representing a custom clickable link for dalamud plugins.
|
||||
/// </summary>
|
||||
DalamudLink,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing any data we don't handle.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a doublewide SE hypen.
|
||||
/// </summary>
|
||||
SeHyphen,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
|
|
@ -14,25 +15,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
/// </summary>
|
||||
public class AutoTranslatePayload : Payload, ITextProvider
|
||||
{
|
||||
public override PayloadType Type => PayloadType.AutoTranslateText;
|
||||
|
||||
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]
|
||||
private uint group;
|
||||
|
|
@ -40,9 +23,8 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
[JsonProperty]
|
||||
private uint key;
|
||||
|
||||
internal AutoTranslatePayload() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||
/// Creates a new auto-translate payload.
|
||||
/// </summary>
|
||||
/// <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.
|
||||
/// There is probably little use to create one of these, however.
|
||||
/// </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.group = group;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
// TODO: friendlier ctor? not sure how to handle that given how weird the tables are
|
||||
|
||||
public override string ToString()
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||
/// </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()
|
||||
{
|
||||
var keyBytes = MakeInteger(this.key);
|
||||
|
|
@ -74,7 +86,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
{
|
||||
START_BYTE,
|
||||
(byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen,
|
||||
(byte)this.group
|
||||
(byte)this.group,
|
||||
};
|
||||
bytes.AddRange(keyBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
|
@ -82,6 +94,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// this seems to always be a bare byte, and not following normal integer encoding
|
||||
|
|
@ -105,7 +118,9 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
// (again, if it's meant for another table)
|
||||
row = sheet.GetRow(this.key);
|
||||
}
|
||||
catch { } // don't care, row will be null
|
||||
catch
|
||||
{
|
||||
} // don't care, row will be null
|
||||
|
||||
if (row?.Group == this.group)
|
||||
{
|
||||
|
|
@ -142,7 +157,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
"TextCommand" => this.DataResolver.GetExcelSheet<TextCommand>().GetRow(this.key).Command,
|
||||
"Tribe" => this.DataResolver.GetExcelSheet<Tribe>().GetRow(this.key).Masculine,
|
||||
"Weather" => this.DataResolver.GetExcelSheet<Weather>().GetRow(this.key).Name,
|
||||
_ => throw new Exception(actualTableName)
|
||||
_ => throw new Exception(actualTableName),
|
||||
};
|
||||
|
||||
value = name;
|
||||
|
|
|
|||
|
|
@ -1,48 +1,62 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads {
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// This class represents a custom Dalamud clickable chat link.
|
||||
/// </summary>
|
||||
public class DalamudLinkPayload : Payload {
|
||||
public class DalamudLinkPayload : Payload
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.DalamudLink;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin command ID to be linked.
|
||||
/// </summary>
|
||||
public uint CommandId { get; internal set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin name to be linked.
|
||||
/// </summary>
|
||||
[NotNull]
|
||||
public string Plugin { get; internal set; } = string.Empty;
|
||||
|
||||
protected override byte[] EncodeImpl() {
|
||||
var pluginBytes = Encoding.UTF8.GetBytes(Plugin);
|
||||
var commandBytes = MakeInteger(CommandId);
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
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;
|
||||
|
||||
if (chunkLen > 255) {
|
||||
if (chunkLen > 255)
|
||||
{
|
||||
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};
|
||||
bytes.Add((byte) pluginBytes.Length);
|
||||
var bytes = new List<byte> { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink };
|
||||
bytes.Add((byte)pluginBytes.Length);
|
||||
bytes.AddRange(pluginBytes);
|
||||
bytes.AddRange(commandBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream) {
|
||||
Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte()));
|
||||
CommandId = GetInteger(reader);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return $"{Type} - Plugin: {Plugin}, Command: {CommandId}";
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte()));
|
||||
this.CommandId = GetInteger(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,47 +14,58 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
public class EmphasisItalicPayload : Payload
|
||||
{
|
||||
/// <summary>
|
||||
/// Payload representing enabling italics on following text.
|
||||
/// </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>
|
||||
/// Initializes a new instance of the <see cref="EmphasisItalicPayload"/> class.
|
||||
/// Creates an EmphasisItalicPayload.
|
||||
/// </summary>
|
||||
/// <param name="enabled">Whether italics formatting should be enabled or disabled for following text.</param>
|
||||
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()
|
||||
{
|
||||
return $"{Type} - Enabled: {IsEnabled}";
|
||||
return $"{this.Type} - Enabled: {this.IsEnabled}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
// realistically this will always be a single byte of value 1 or 2
|
||||
// but we'll treat it normally anyway
|
||||
var enabledBytes = MakeInteger(IsEnabled ? (uint)1 : 0);
|
||||
var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0);
|
||||
|
||||
var chunkLen = enabledBytes.Length + 1;
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen
|
||||
START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen,
|
||||
};
|
||||
bytes.AddRange(enabledBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
|
@ -62,9 +73,10 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
|||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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.IO;
|
||||
using System;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads {
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
/// <summary>
|
||||
/// SeString payload representing a bitmap icon from fontIcon
|
||||
/// SeString payload representing a bitmap icon from fontIcon.
|
||||
/// </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>
|
||||
/// 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>
|
||||
[Obsolete("Use IconPayload.Icon")]
|
||||
public uint IconIndex => (uint) Icon;
|
||||
public uint IconIndex => (uint)this.Icon;
|
||||
|
||||
/// <summary>
|
||||
/// Icon the payload represents.
|
||||
/// Gets or sets the icon the payload represents.
|
||||
/// </summary>
|
||||
public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None;
|
||||
|
||||
internal IconPayload() { }
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// Create a Icon payload for the specified icon.
|
||||
/// </summary>
|
||||
/// <param name="icon">The Icon</param>
|
||||
public IconPayload(BitmapFontIcon icon) {
|
||||
Icon = icon;
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - {this.Icon}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PayloadType Type => PayloadType.Icon;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override byte[] EncodeImpl() {
|
||||
var indexBytes = MakeInteger((uint) this.Icon);
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var indexBytes = MakeInteger((uint)this.Icon);
|
||||
var chunkLen = indexBytes.Length + 1;
|
||||
var bytes = new List<byte>(new byte[] {
|
||||
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen
|
||||
var bytes = new List<byte>(new byte[]
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen,
|
||||
});
|
||||
bytes.AddRange(indexBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
|
@ -53,14 +73,9 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream) {
|
||||
Icon = (BitmapFontIcon) GetInteger(reader);
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.Icon = (BitmapFontIcon)GetInteger(reader);
|
||||
}
|
||||
|
||||
/// <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