HUDManager/HUD Manager/Hud.cs

146 lines
5.0 KiB
C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using HUD_Manager.Structs;
namespace HUD_Manager {
public class Hud {
// Updated 5.45
// This is 81 elements * 36 bytes each. Each element is 32 bytes in ADDON.DAT,
// but they're 36 bytes when loaded into memory.
private const int LayoutSize = 0xb64;
// Updated 5.4
private const int SlotOffset = 0x59e8;
private delegate IntPtr GetFilePointerDelegate(byte index);
private delegate uint SetHudLayoutDelegate(IntPtr filePtr, uint hudLayout, byte unk0, byte unk1);
private readonly GetFilePointerDelegate? _getFilePointer;
private readonly SetHudLayoutDelegate? _setHudLayout;
private Plugin Plugin { get; }
public Hud(Plugin plugin) {
this.Plugin = plugin;
var getFilePointerPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 85 C0 74 14 83 7B 44 00");
var setHudLayoutPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 33 C0 EB 15");
if (getFilePointerPtr != IntPtr.Zero) {
this._getFilePointer = Marshal.GetDelegateForFunctionPointer<GetFilePointerDelegate>(getFilePointerPtr);
}
if (setHudLayoutPtr != IntPtr.Zero) {
this._setHudLayout = Marshal.GetDelegateForFunctionPointer<SetHudLayoutDelegate>(setHudLayoutPtr);
}
}
private IntPtr GetFilePointer(byte index) {
return this._getFilePointer?.Invoke(index) ?? IntPtr.Zero;
}
public void SelectSlot(HudSlot slot, bool force = false) {
if (this._setHudLayout == null) {
return;
}
var file = this.GetFilePointer(0);
// change the current slot so the game lets us pick one that's currently in use
if (!force) {
goto Return;
}
var currentSlotPtr = this.GetDataPointer() + SlotOffset;
// read the current slot
var currentSlot = (uint) Marshal.ReadInt32(currentSlotPtr);
// change it to a different slot
if (currentSlot == (uint) slot) {
if (currentSlot < 3) {
currentSlot += 1;
} else {
currentSlot = 0;
}
// back up this different slot
var backup = this.ReadLayout((HudSlot) currentSlot);
// change the current slot in memory
Marshal.WriteInt32(currentSlotPtr, (int) currentSlot);
// ask the game to change slot to our desired slot
// for some reason, this overwrites the current slot, so this is why we back up
this._setHudLayout.Invoke(file, (uint) slot, 0, 1);
// restore the backup
this.WriteLayout((HudSlot) currentSlot, backup);
return;
}
Return:
this._setHudLayout.Invoke(file, (uint) slot, 0, 1);
}
private IntPtr GetDataPointer() {
var dataPtr = this.GetFilePointer(0) + 0x50;
return Marshal.ReadIntPtr(dataPtr);
}
internal IntPtr GetLayoutPointer(HudSlot slot) {
var slotNum = (int) slot;
return this.GetDataPointer() + 0x2c58 + slotNum * LayoutSize;
}
public HudSlot GetActiveHudSlot() {
var slotVal = Marshal.ReadInt32(this.GetDataPointer() + SlotOffset);
if (!Enum.IsDefined(typeof(HudSlot), slotVal)) {
throw new IOException($"invalid hud slot in FFXIV memory of ${slotVal}");
}
return (HudSlot) slotVal;
}
public Layout ReadLayout(HudSlot slot) {
var slotPtr = this.GetLayoutPointer(slot);
return Marshal.PtrToStructure<Layout>(slotPtr);
}
public void WriteLayout(HudSlot slot, Layout layout) {
var slotPtr = this.GetLayoutPointer(slot);
var dict = layout.ToDictionary();
// update existing elements with saved data instead of wholesale overwriting
var slotLayout = this.ReadLayout(slot);
for (var i = 0; i < slotLayout.elements.Length; i++) {
if (dict.TryGetValue(slotLayout.elements[i].id, out var element)) {
slotLayout.elements[i] = element;
}
}
Marshal.StructureToPtr(slotLayout, slotPtr, false);
// copy directly over
// Marshal.StructureToPtr(layout, slotPtr, false);
var currentSlot = this.GetActiveHudSlot();
if (currentSlot == slot) {
this.SelectSlot(currentSlot, true);
}
}
}
public enum HudSlot {
One = 0,
Two = 1,
Three = 2,
Four = 3,
}
public class Vector2<T> {
public T X { get; }
public T Y { get; }
public Vector2(T x, T y) {
this.X = x;
this.Y = y;
}
}
}