2020-07-29 16:01:00 +00:00
|
|
|
|
using Dalamud.Plugin;
|
2020-07-31 17:29:40 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2020-07-29 16:01:00 +00:00
|
|
|
|
using System;
|
2020-08-04 02:13:29 +00:00
|
|
|
|
using System.Collections.Generic;
|
2020-07-31 17:29:40 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.IO.Compression;
|
2020-07-29 16:01:00 +00:00
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
|
|
namespace HudSwap {
|
|
|
|
|
public class HUD {
|
2020-07-30 01:01:22 +00:00
|
|
|
|
private const int LAYOUT_SIZE = 0xb40;
|
|
|
|
|
|
2020-07-29 16:01:00 +00:00
|
|
|
|
private delegate IntPtr GetFilePointerDelegate(byte index);
|
|
|
|
|
private delegate uint SetHudLayoutDelegate(IntPtr filePtr, uint hudLayout, byte unk0, byte unk1);
|
|
|
|
|
|
2020-07-31 17:58:56 +00:00
|
|
|
|
private readonly GetFilePointerDelegate _getFilePointer;
|
|
|
|
|
private readonly SetHudLayoutDelegate _setHudLayout;
|
2020-07-29 16:01:00 +00:00
|
|
|
|
|
2020-07-29 16:30:14 +00:00
|
|
|
|
private readonly DalamudPluginInterface pi;
|
2020-07-29 16:01:00 +00:00
|
|
|
|
|
|
|
|
|
public HUD(DalamudPluginInterface pi) {
|
2020-07-31 17:58:56 +00:00
|
|
|
|
this.pi = pi ?? throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
|
2020-07-29 16:01:00 +00:00
|
|
|
|
IntPtr getFilePointerPtr = this.pi.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 85 C0 74 14 83 7B 44 00");
|
|
|
|
|
IntPtr setHudLayoutPtr = this.pi.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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-30 01:01:22 +00:00
|
|
|
|
public uint SelectSlot(HudSlot slot, bool force = false) {
|
2020-07-29 16:01:00 +00:00
|
|
|
|
IntPtr file = this.GetFilePointer(0);
|
2020-07-30 17:21:08 +00:00
|
|
|
|
// change the current slot so the game lets us pick one that's currently in use
|
2020-07-30 01:01:22 +00:00
|
|
|
|
if (force) {
|
|
|
|
|
IntPtr currentSlotPtr = this.GetDataPointer() + 0x5958;
|
2020-07-30 17:21:08 +00:00
|
|
|
|
// read the current slot
|
2020-07-30 01:01:22 +00:00
|
|
|
|
uint currentSlot = (uint)Marshal.ReadInt32(currentSlotPtr);
|
2020-07-30 17:21:08 +00:00
|
|
|
|
// change it to a different slot
|
|
|
|
|
if (currentSlot == (uint)slot) {
|
|
|
|
|
if (currentSlot < 3) {
|
|
|
|
|
currentSlot += 1;
|
|
|
|
|
} else {
|
|
|
|
|
currentSlot = 0;
|
|
|
|
|
}
|
|
|
|
|
// back up this different slot
|
|
|
|
|
byte[] 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
|
|
|
|
|
uint res = this._setHudLayout.Invoke(file, (uint)slot, 0, 1);
|
|
|
|
|
// restore the backup
|
|
|
|
|
this.WriteLayout((HudSlot)currentSlot, backup);
|
|
|
|
|
return res;
|
2020-07-30 01:01:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this._setHudLayout.Invoke(file, (uint)slot, 0, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IntPtr GetDataPointer() {
|
|
|
|
|
IntPtr dataPtr = this.GetFilePointer(0) + 0x50;
|
|
|
|
|
return Marshal.ReadIntPtr(dataPtr);
|
2020-07-29 16:01:00 +00:00
|
|
|
|
}
|
2020-07-30 01:01:22 +00:00
|
|
|
|
|
|
|
|
|
private IntPtr GetLayoutPointer(HudSlot slot) {
|
|
|
|
|
int slotNum = (int)slot;
|
|
|
|
|
return this.GetDataPointer() + 0x2c58 + (slotNum * LAYOUT_SIZE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] ReadLayout(HudSlot slot) {
|
|
|
|
|
IntPtr slotPtr = this.GetLayoutPointer(slot);
|
|
|
|
|
byte[] bytes = new byte[LAYOUT_SIZE];
|
|
|
|
|
Marshal.Copy(slotPtr, bytes, 0, LAYOUT_SIZE);
|
|
|
|
|
return bytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteLayout(HudSlot slot, byte[] layout) {
|
2020-07-31 17:58:56 +00:00
|
|
|
|
if (layout == null) {
|
|
|
|
|
throw new ArgumentNullException(nameof(layout), "layout cannot be null");
|
|
|
|
|
}
|
2020-07-30 01:01:22 +00:00
|
|
|
|
if (layout.Length != LAYOUT_SIZE) {
|
|
|
|
|
throw new ArgumentException($"layout must be {LAYOUT_SIZE} bytes", nameof(layout));
|
|
|
|
|
}
|
|
|
|
|
IntPtr slotPtr = this.GetLayoutPointer(slot);
|
|
|
|
|
Marshal.Copy(layout, 0, slotPtr, LAYOUT_SIZE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public enum HudSlot {
|
|
|
|
|
One = 0,
|
|
|
|
|
Two = 1,
|
|
|
|
|
Three = 2,
|
|
|
|
|
Four = 3,
|
2020-07-29 16:01:00 +00:00
|
|
|
|
}
|
2020-07-31 17:29:40 +00:00
|
|
|
|
|
2020-08-04 02:13:29 +00:00
|
|
|
|
public class Vector2<T> {
|
|
|
|
|
public T X { get; private set; }
|
|
|
|
|
public T Y { get; private set; }
|
|
|
|
|
|
|
|
|
|
public Vector2(T x, T y) {
|
|
|
|
|
this.X = x;
|
|
|
|
|
this.Y = y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 17:29:40 +00:00
|
|
|
|
[Serializable]
|
|
|
|
|
public class SharedLayout {
|
|
|
|
|
[JsonProperty]
|
|
|
|
|
private readonly byte[] compressed;
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private byte[] uncompressed = null;
|
|
|
|
|
|
2020-08-04 02:13:29 +00:00
|
|
|
|
public Dictionary<string, Vector2<short>> Positions { get; private set; }
|
|
|
|
|
|
2020-07-31 17:58:56 +00:00
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "nah")]
|
2020-07-31 17:29:40 +00:00
|
|
|
|
public byte[] Layout() {
|
|
|
|
|
if (this.uncompressed != null) {
|
|
|
|
|
return this.uncompressed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
using (MemoryStream compressed = new MemoryStream(this.compressed)) {
|
|
|
|
|
using (GZipStream gzip = new GZipStream(compressed, CompressionMode.Decompress)) {
|
|
|
|
|
using (MemoryStream uncompressed = new MemoryStream()) {
|
|
|
|
|
gzip.CopyTo(uncompressed);
|
|
|
|
|
this.uncompressed = uncompressed.ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.uncompressed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[JsonConstructor]
|
|
|
|
|
private SharedLayout() {
|
|
|
|
|
// For JSON
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-04 02:13:29 +00:00
|
|
|
|
public SharedLayout(Layout layout) {
|
|
|
|
|
if (layout == null) {
|
|
|
|
|
throw new ArgumentNullException(nameof(layout), "Layout cannot be null");
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 17:29:40 +00:00
|
|
|
|
using (MemoryStream compressed = new MemoryStream()) {
|
|
|
|
|
using (GZipStream gzip = new GZipStream(compressed, CompressionLevel.Optimal)) {
|
2020-08-04 02:13:29 +00:00
|
|
|
|
using (MemoryStream uncompressed = new MemoryStream(layout.Hud)) {
|
2020-07-31 17:29:40 +00:00
|
|
|
|
uncompressed.CopyTo(gzip);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.compressed = compressed.ToArray();
|
|
|
|
|
}
|
2020-08-04 02:13:29 +00:00
|
|
|
|
|
|
|
|
|
this.Positions = layout.Positions;
|
2020-07-31 17:29:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-29 16:01:00 +00:00
|
|
|
|
}
|