Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/awgil/ffxiv_bossmod into wip
Browse files Browse the repository at this point in the history
  • Loading branch information
xanunderscore committed Nov 6, 2024
2 parents 09ef24a + 5e24287 commit cbd7842
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 11 deletions.
21 changes: 19 additions & 2 deletions BossMod/Framework/WorldStateGameSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.Interop;
using System.Text;

namespace BossMod;

Expand Down Expand Up @@ -70,8 +71,13 @@ public unsafe WorldStateGameSync(WorldState ws, ActionManagerEx amex)
_startTime = DateTime.Now;
_startQPC = Framework.Instance()->PerformanceCounterValue;
_interceptor.ServerIPCReceived += ServerIPCReceived;
_interceptor.ClientIPCSent += ClientIPCSent;

_netConfig = Service.Config.GetAndSubscribe<ReplayManagementConfig>(config => _interceptor.Active = config.RecordServerPackets || config.DumpServerPackets);
_netConfig = Service.Config.GetAndSubscribe<ReplayManagementConfig>(config =>
{
_interceptor.ActiveRecv = config.RecordServerPackets || config.DumpServerPackets;
_interceptor.ActiveSend = config.DumpClientPackets;
});
_subscriptions = new
(
amex.ActionRequestExecuted.Subscribe(OnActionRequested),
Expand Down Expand Up @@ -685,10 +691,21 @@ private unsafe void ServerIPCReceived(DateTime sendTimestamp, uint sourceServerA
var ipc = new NetworkState.ServerIPC(id, opcode, epoch, sourceServerActor, sendTimestamp, [.. payload]);
if (_netConfig.Data.RecordServerPackets)
_globalOps.Add(new NetworkState.OpServerIPC(ipc));
if (_netConfig.Data.DumpServerPackets)
if (_netConfig.Data.DumpServerPackets && (!_netConfig.Data.DumpServerPacketsPlayerOnly || sourceServerActor == UIState.Instance()->PlayerState.EntityId))
_decoder.LogNode(_decoder.Decode(ipc, DateTime.UtcNow), "");
}

private unsafe void ClientIPCSent(uint opcode, Span<byte> payload)
{
if (_netConfig.Data.DumpClientPackets)
{
var sb = new StringBuilder($"Client IPC [0x{opcode:X4}]: data=");
foreach (byte b in payload)
sb.Append($"{b:X2}");
_decoder.LogNode(new(sb.ToString()), "");
}
}

private void OnActionRequested(ClientActionRequest arg)
{
_globalOps.Add(new ClientState.OpActionRequest(arg));
Expand Down
32 changes: 32 additions & 0 deletions BossMod/Network/PacketDecoder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using BossMod.Network.ServerIPC;
using Dalamud.Memory;
using Dalamud.Utility;
using System.Runtime.CompilerServices;
using System.Text;

Expand Down Expand Up @@ -54,6 +55,10 @@ public void LogNode(TextNode n, string prefix)
PacketID.RSVData when (RSVData*)payload is var p => new($"{MemoryHelper.ReadStringNullTerminated((nint)p->Key)} = {MemoryHelper.ReadString((nint)p->Value, p->ValueLength)} [{p->ValueLength}]"),
PacketID.Countdown when (Countdown*)payload is var p => new($"{p->Time}s from {DecodeActor(p->SenderID)}{(p->FailedInCombat != 0 ? " fail-in-combat" : "")} '{MemoryHelper.ReadStringNullTerminated((nint)p->Text)}' u={p->u4:X4} {p->u9:X2} {p->u10:X2}"),
PacketID.CountdownCancel when (CountdownCancel*)payload is var p => new($"from {DecodeActor(p->SenderID)} '{MemoryHelper.ReadStringNullTerminated((nint)p->Text)}' u={p->u4:X4} {p->u6:X4}"),
PacketID.MarketBoardItemListingCount when (MarketBoardItemListingCount*)payload is var p => new($"{p->NumItems} items, {p->Error:X} error"),
PacketID.MarketBoardItemListing when (MarketBoardItemListing*)payload is var p => DecodeMarketBoardItemListing(p),
PacketID.MarketBoardPurchase when (MarketBoardPurchase*)payload is var p => new($"{p->Quantity}x {p->ItemId} (stackable={p->Stackable}), error={Utils.LogMessageString(p->ErrorLogId)}"),
PacketID.RetainerState when (RetainerState*)payload is var p => new($"{p->RetainerId:X} '{MemoryHelper.ReadStringNullTerminated((nint)p->Name)}', change={p->StateChange}, flags={p->Flags:X16} (town={p->Town}, selling={p->IsSelling}), custom message={Utils.LogMessageString(p->CustomMessageId)}"),
PacketID.StatusEffectList when (StatusEffectList*)payload is var p => DecodeStatusEffectList(p),
PacketID.StatusEffectListEureka when (StatusEffectListEureka*)payload is var p => DecodeStatusEffectList(&p->Data, $", rank={p->Rank}/{p->Element}/{p->u2}, pad={p->pad3:X2}"),
PacketID.StatusEffectListBozja when (StatusEffectListBozja*)payload is var p => DecodeStatusEffectList(&p->Data, $", rank={p->Rank}, pad={p->pad1:X2}{p->pad2:X4}"),
Expand Down Expand Up @@ -89,6 +94,10 @@ PacketID.SpawnObject when (SpawnObject*)payload is var p => DecodeSpawnObject(p)
PacketID.UpdateClassInfo when (UpdateClassInfo*)payload is var p => DecodeUpdateClassInfo(p),
PacketID.UpdateClassInfoEureka when (UpdateClassInfoEureka*)payload is var p => DecodeUpdateClassInfo(&p->Data, $", rank={p->Rank}/{p->Element}/{p->u2}, pad={p->pad3:X2}"),
PacketID.UpdateClassInfoBozja when (UpdateClassInfoBozja*)payload is var p => DecodeUpdateClassInfo(&p->Data, $", rank={p->Rank}, pad={p->pad1:X2}{p->pad2:X4}"),
PacketID.RetainerSummary when (RetainerSummary*)payload is var p => new($"#{p->SequenceId}, {p->NumInformationPackets} packets, {p->MaxRetainerEntitlement} max retainers, response={p->IsResponseToServerCallbackRequest != 0} (listener index={p->ServerCallbackListenerIndex})"),
PacketID.RetainerInformation when (RetainerInformation*)payload is var p => new($"#{p->SequenceId}.{p->Index}: {p->RetainerId:X} '{MemoryHelper.ReadSeStringNullTerminated((nint)p->Name)}', {p->NumItemsInInventory}/{p->NumItemsOnMarket} items, avail={p->Available}, u={p->Unk2A} {p->Unk2C}"),
PacketID.ItemMarketBoardSummary when (ItemMarketBoardSummary*)payload is var p => new($"#{p->SequenceId}, {p->NumItemPackets} items"),
PacketID.ItemMarketBoardInfo when (ItemMarketBoardInfo*)payload is var p => new($"#{p->SequenceId}, at {p->InventoryType}.{p->Slot}, unk={p->Unk10} {p->Unk18}"),
PacketID.EventPlay when (EventPlayN*)payload is var p => DecodeEventPlay(p, Math.Min((int)p->PayloadLength, 1)),
PacketID.EventPlay4 when (EventPlayN*)payload is var p => DecodeEventPlay(p, Math.Min((int)p->PayloadLength, 4)),
PacketID.EventPlay8 when (EventPlayN*)payload is var p => DecodeEventPlay(p, Math.Min((int)p->PayloadLength, 8)),
Expand All @@ -97,6 +106,7 @@ PacketID.EventPlay32 when (EventPlayN*)payload is var p => DecodeEventPlay(p, Ma
PacketID.EventPlay64 when (EventPlayN*)payload is var p => DecodeEventPlay(p, Math.Min((int)p->PayloadLength, 64)),
PacketID.EventPlay128 when (EventPlayN*)payload is var p => DecodeEventPlay(p, Math.Min((int)p->PayloadLength, 128)),
PacketID.EventPlay255 when (EventPlayN*)payload is var p => DecodeEventPlay(p, Math.Min((int)p->PayloadLength, 255)),
PacketID.ServerRequestCallbackResponse1 when (DecodeServerRequestCallbackResponse*)payload is var p => DecodeServerRequestCallbackResponse(p),
PacketID.EnvControl when (EnvControl*)payload is var p => new($"{p->DirectorID:X8}.{p->Index} = {p->State1:X4} {p->State2:X4}, pad={p->pad9:X2} {p->padA:X4} {p->padC:X8}"),
PacketID.NpcYell when (NpcYell*)payload is var p => new($"{DecodeActor(p->SourceID)}: {p->Message} '{Service.LuminaRow<Lumina.Excel.GeneratedSheets.NpcYell>(p->Message)?.Text}' (u8={p->u8}, uE={p->uE}, u10={p->u10}, u18={p->u18})"),
PacketID.WaymarkPreset when (WaymarkPreset*)payload is var p => DecodeWaymarkPreset(p),
Expand All @@ -105,6 +115,19 @@ PacketID.Waymark when (ServerIPC.Waymark*)payload is var p => DecodeWaymark(p),
_ => null
};

private TextNode DecodeMarketBoardItemListing(MarketBoardItemListing* p)
{
var res = new TextNode($"request {p->RequestId}, page {p->FirstPageIndex}-{p->NextPageIndex}");
for (int i = 0; i < 10; ++i)
{
var item = (MarketBoardItemListingEntry*)p->EntriesRaw + i;
var s1 = MemoryHelper.ReadString((nint)item + 0x46, 32);
var s2 = MemoryHelper.ReadString((nint)item + 0x66, 32);
res.AddChild($"{item->ListingId:X}: {item->Quantity}x {item->ItemId} @ {item->UnitPrice}+{item->TotalTax}, cont={item->ContainerId}, nmat={item->MateriaCount}, s1={s1}, s2={s2}, unks={item->Unk40:X8} {item->Unk44:X4} {item->Unk88:X2} {item->Unk8C:X8}");
}
return res;
}

private TextNode DecodeStatusEffectList(StatusEffectList* p, string extra = "")
{
var res = new TextNode($"L{p->Level} {p->ClassID}, hp={p->CurHP}/{p->MaxHP}, mp={p->CurMP}/{p->MaxMP}, shield={p->ShieldValue}%{extra}, u={p->u2:X2} {p->u3:X2} {p->u12:X4} {p->u17C:X8}");
Expand Down Expand Up @@ -198,6 +221,7 @@ private TextNode DecodeActorControl(ActorControlCategory category, uint p1, uint
ActorControlCategory.LimitBreakGauge => $"{p1} bars, {p2}/{p3}, uE={p4}, uF={p5}",
ActorControlCategory.AchievementProgress => $"{p1} '{Service.LuminaRow<Lumina.Excel.GeneratedSheets.Achievement>(p1)?.Name}': {p2}/{p3}",
ActorControlCategory.ActionRejected => $"{Utils.LogMessageString(p1)}; action={new ActionID((ActionType)p2, p3)}, recast={p4 * 0.01f:f2}/{p5 * 0.01f:f2}, src-seq={p6}",
ActorControlCategory.ServerRequestCallbackResponse => $"listener index={p1}, listener request type={p2}, data={p3} {p4} {p5} {p6}",
ActorControlCategory.SetDutyActionSet => $"row={p1}",
ActorControlCategory.SetDutyActionDetails => $"slot0: {new ActionID(ActionType.Spell, p1)} ({p5}/{p2} charges), slot1: {new ActionID(ActionType.Spell, p3)} ({p6}/{p4} charges)",
ActorControlCategory.SetDutyActionPresent => $"value={p1}",
Expand Down Expand Up @@ -317,6 +341,14 @@ private TextNode DecodeEventPlay(EventPlayN* p, int payloadLength)
return res;
}

private TextNode DecodeServerRequestCallbackResponse(DecodeServerRequestCallbackResponse* p)
{
var sb = new StringBuilder($"listener index={p->ListenerIndex}, listener request type={p->ListenerRequestType}, data=[{p->DataCount}]");
for (int i = 0; i < p->DataCount; ++i)
sb.Append($" {p->Data[i]}");
return new(sb.ToString());
}

private TextNode DecodeWaymarkPreset(WaymarkPreset* p)
{
var res = new TextNode($"pad={p->pad1:X2} {p->pad2:X4}");
Expand Down
44 changes: 40 additions & 4 deletions BossMod/Network/PacketInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,51 @@ unsafe struct ReceivedPacket
[FieldOffset(0x18)] public long SendTimestamp;
}

[StructLayout(LayoutKind.Explicit, Size = 0x10)]
unsafe struct SentIPCHeader
{
[FieldOffset(0x00)] public uint Opcode;
[FieldOffset(0x08)] public ulong PayloadSize; // 0x10 (payload header) + actual data size
}

internal sealed class PacketInterceptor : IDisposable
{
public delegate void ServerIPCReceivedDelegate(DateTime sendTimestamp, uint sourceServerActor, uint targetServerActor, ushort opcode, uint epoch, Span<byte> payload);
public event ServerIPCReceivedDelegate? ServerIPCReceived;

public delegate void ClientIPCSentDelegate(uint opcode, Span<byte> payload);
public event ClientIPCSentDelegate? ClientIPCSent;

private unsafe delegate bool FetchReceivedPacketDelegate(void* self, ReceivedPacket* outData);
private readonly HookAddress<FetchReceivedPacketDelegate>? _fetchHook;

public bool Active
private unsafe delegate byte SendPacketDelegate(void* self, SentIPCHeader* packet, int* a3, byte a4);
private readonly HookAddress<SendPacketDelegate>? _sendHook;

public bool ActiveRecv
{
get => _fetchHook?.Enabled ?? false;
set
{
if (_fetchHook == null)
Service.Log($"[NPI] Hook not found!");
Service.Log($"[NPI] Recv hook not found!");
else
_fetchHook.Enabled = value;
}
}

public bool ActiveSend
{
get => _sendHook?.Enabled ?? false;
set
{
if (_sendHook == null)
Service.Log($"[NPI] Send hook not found!");
else
_sendHook.Enabled = value;
}
}

public unsafe PacketInterceptor()
{
// alternative signatures - seem to be changing from build to build:
Expand All @@ -49,12 +74,17 @@ public unsafe PacketInterceptor()
if (foundFetchAddress)
_fetchHook = new(fetchAddress, FetchReceivedPacketDetour, false);

_sendHook = new("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70", SendPacketDetour, false);

// potentially useful sigs from dalamud:
// server ipc handler: 40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 8B F2 --- void(void* self, uint targetId, void* dataPtr)
// client ipc handler: 48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ?? --- byte(void* self, void* dataPtr, void* a3, byte a4)
}

public void Dispose() => _fetchHook?.Dispose();
public void Dispose()
{
_fetchHook?.Dispose();
_sendHook?.Dispose();
}

private unsafe bool FetchReceivedPacketDetour(void* self, ReceivedPacket* outData)
{
Expand All @@ -71,4 +101,10 @@ private unsafe bool FetchReceivedPacketDetour(void* self, ReceivedPacket* outDat
}
return res;
}

private unsafe byte SendPacketDetour(void* self, SentIPCHeader* packet, int* a3, byte a4)
{
ClientIPCSent?.Invoke(packet->Opcode, new((byte*)packet + 0x20, (int)packet->PayloadSize - 0x10));
return _sendHook!.Original(self, packet, a3, a4);
}
}
Loading

0 comments on commit cbd7842

Please sign in to comment.