feat: add battle talk stuff

This commit is contained in:
Anna 2021-04-10 16:48:32 -04:00
parent e713970249
commit a51498b41a
4 changed files with 152 additions and 3 deletions

117
XivCommon/Functions/BattleTalk.cs Executable file
View File

@ -0,0 +1,117 @@
using System;
using System.Text;
using Dalamud.Game;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.Plugin;
namespace XivCommon.Functions {
public class BattleTalk : IDisposable {
private GameFunctions Functions { get; }
private SeStringManager SeStringManager { get; }
public delegate void BattleTalkEventDelegate(ref SeString sender, ref SeString message, ref BattleTalkOptions options, ref bool isHandled);
public event BattleTalkEventDelegate? OnBattleTalk;
private delegate byte AddBattleTalkDelegate(IntPtr uiModule, IntPtr sender, IntPtr message, float duration, byte style);
private Hook<AddBattleTalkDelegate> AddBattleTextHook { get; }
internal BattleTalk(GameFunctions functions, SigScanner scanner, SeStringManager seStringManager) {
this.Functions = functions;
this.SeStringManager = seStringManager;
var addBattleTextPtr = scanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 50 48 8B 01 49 8B D8 0F 29 74 24 ?? 48 8B FA 0F 28 F3 FF 50 40 C7 44 24 ?? ?? ?? ?? ??");
this.AddBattleTextHook = new Hook<AddBattleTalkDelegate>(addBattleTextPtr, new AddBattleTalkDelegate(this.AddBattleTalkDetour));
this.AddBattleTextHook.Enable();
}
public void Dispose() {
this.AddBattleTextHook.Dispose();
}
private unsafe byte AddBattleTalkDetour(IntPtr uiModule, IntPtr senderPtr, IntPtr messagePtr, float duration, byte style) {
var rawSender = Util.ReadTerminated(senderPtr);
var rawMessage = Util.ReadTerminated(messagePtr);
var sender = this.SeStringManager.Parse(rawSender);
var message = this.SeStringManager.Parse(rawMessage);
var options = new BattleTalkOptions {
Duration = duration,
Style = (BattleTalkStyle) style,
};
var handled = false;
try {
this.OnBattleTalk?.Invoke(ref sender, ref message, ref options, ref handled);
} catch (Exception ex) {
PluginLog.Log(ex, "Exception in BattleTalk detour");
}
if (handled) {
return 0;
}
var finalSender = sender.Encode().Terminate();
var finalMessage = message.Encode().Terminate();
fixed (byte* fSenderPtr = finalSender, fMessagePtr = finalMessage) {
return this.AddBattleTextHook.Original(uiModule, (IntPtr) fSenderPtr, (IntPtr) fMessagePtr, options.Duration, (byte) options.Style);
}
}
public void Show(SeString sender, SeString message, BattleTalkOptions? options = null) {
this.Show(sender.Encode(), message.Encode(), options);
}
public void Show(string sender, string message, BattleTalkOptions? options = null) {
this.Show(Encoding.UTF8.GetBytes(sender), Encoding.UTF8.GetBytes(message), options);
}
public void Show(SeString sender, string message, BattleTalkOptions? options = null) {
this.Show(sender.Encode(), Encoding.UTF8.GetBytes(message), options);
}
public void Show(string sender, SeString message, BattleTalkOptions? options = null) {
this.Show(Encoding.UTF8.GetBytes(sender), message.Encode(), options);
}
private void Show(byte[] sender, byte[] message, BattleTalkOptions? options) {
if (sender.Length == 0) {
throw new ArgumentException("sender cannot be empty", nameof(sender));
}
if (message.Length == 0) {
throw new ArgumentException("message cannot be empty", nameof(message));
}
options ??= new BattleTalkOptions();
var uiModule = this.Functions.GetUiModule();
unsafe {
fixed (byte* senderPtr = sender.Terminate(), messagePtr = message.Terminate()) {
this.AddBattleTalkDetour(uiModule, (IntPtr) senderPtr, (IntPtr) messagePtr, options.Duration, (byte) options.Style);
}
}
}
}
public class BattleTalkOptions {
/// <summary>
/// Duration of the window in seconds.
/// </summary>
public float Duration { get; set; } = 5f;
public BattleTalkStyle Style { get; set; } = BattleTalkStyle.Normal;
}
public enum BattleTalkStyle : byte {
Normal = 0,
Aetherial = 6,
System = 7,
Blue = 9,
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Game.Text.SeStringHandling;
using XivCommon.Functions;
namespace XivCommon {
@ -13,8 +14,9 @@ namespace XivCommon {
public Chat Chat { get; }
public PartyFinder PartyFinder { get; }
public BattleTalk BattleTalk { get; }
internal GameFunctions(SigScanner scanner) {
internal GameFunctions(SigScanner scanner, SeStringManager seStringManager) {
this.UiModulePtr = scanner.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 8D 54 24 ?? 48 83 C1 10 E8 ?? ?? ?? ??");
var getUiModulePtr = scanner.ScanText("E8 ?? ?? ?? ?? 83 3B 01");
@ -22,9 +24,11 @@ namespace XivCommon {
this.Chat = new Chat(this, scanner);
this.PartyFinder = new PartyFinder(scanner);
this.BattleTalk = new BattleTalk(this, scanner, seStringManager);
}
public void Dispose() {
this.BattleTalk.Dispose();
this.PartyFinder.Dispose();
}

26
XivCommon/Util.cs Executable file
View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
namespace XivCommon {
internal static class Util {
public static byte[] Terminate(this byte[] array) {
var terminated = new byte[array.Length + 1];
Array.Copy(array, terminated, array.Length);
terminated[terminated.Length - 1] = 0;
return terminated;
}
public static unsafe byte[] ReadTerminated(IntPtr memory) {
var buf = new List<byte>();
var ptr = (byte*) memory;
while (*ptr != 0) {
buf.Add(*ptr);
ptr += 1;
}
return buf.ToArray();
}
}
}

View File

@ -1,12 +1,14 @@
using System;
using Dalamud.Game;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin;
namespace XivCommon {
public class XivCommonBase : IDisposable {
public GameFunctions Functions { get; }
public XivCommonBase(SigScanner scanner) {
this.Functions = new GameFunctions(scanner);
public XivCommonBase(DalamudPluginInterface @interface) {
this.Functions = new GameFunctions(@interface.TargetModuleScanner, @interface.SeStringManager);
}
public void Dispose() {