Files

104 lines
4.6 KiB
C#

using System.Runtime.InteropServices;
using Dalamud;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using ICRebuild.Core;
namespace ICRebuild.Runtime;
// Shared Zone hooks; acquire/release from module lifecycle code, never from callbacks.
public sealed unsafe class NetworkManager : IDisposable
{
public const string UpSignature="48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70";
// Stack-save offset 18 is intentional: wildcarding it also matches chat receive.
public const string DownSignature="48 89 5C 24 18 57 48 83 EC 20 48 83 B9 88 00 00 00 00 48 8B FA 48 8B D9";
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate byte ZoneUp(nint connection,nint data,nint arg,byte flag);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate byte ZoneReceive(nint connection,nint holder);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate byte ReceiverSendPacket(nint receiver,nint packet,uint a3,uint a4);
private readonly SharedModuleResource hooks;
private readonly GameContext game;
private readonly string version;
private readonly PacketMemoryReader memory=SafeMemory.ReadBytes;
private ReceiverSendPacket? receiverSend;
private bool disposed;
public NetworkPipeline Pipeline { get; }
public ActivePacketDispatcher ActiveSender { get; }
public bool CanSendNative => receiverSend != null;
public NetworkManager(GameContext game,string version)
{
this.game=game; this.version=version;
Pipeline=new(version); ActiveSender=new(version); hooks=new(Start);
}
public void Observe(ResourceScope owner,NetworkPhase phase,Action<NetworkObservation> callback,int? opcode=null)
{
ObjectDisposedException.ThrowIf(disposed,this);
var subscription=new ResourceScope();
try
{
hooks.Acquire(subscription);
subscription.Own(Pipeline.Subscribe(phase,callback,opcode));
owner.Own(subscription);
}
catch { subscription.Dispose(); throw; }
}
public SendTransactionSnapshot EnqueueSend(EncodedGamePacket packet)
{
ObjectDisposedException.ThrowIf(disposed,this);
if(receiverSend==null)
throw new InvalidOperationException("请先开启 36(网络记录/管线)并确认主动发送入口已就绪");
return ActiveSender.Enqueue(packet,game.ReadSendSession());
}
public SendTransactionSnapshot? PumpActiveSends()
{
if(disposed) return null;
return ActiveSender.Pump(game.ReadSendSession(),SendNative);
}
private byte SendNative(ReadOnlySpan<byte> nativeBuffer)
{
var sender=receiverSend;
if(sender==null || nativeBuffer.Length<NativeSendPacket.NativePrefixSize) return 0;
var fw=Framework.Instance();
if(fw==null || fw->NetworkModuleProxy==null) return 0;
var receiver=*(nint*)((nint)fw->NetworkModuleProxy+8);
if(receiver==0) return 0;
var padded=new byte[Math.Max(0xF60,nativeBuffer.Length)];
nativeBuffer.CopyTo(padded);
fixed(byte* ptr=padded)
{
return sender(receiver,(nint)ptr,0,0);
}
}
private void Start(ResourceScope scope)
{
if(!NativePacketCapture.IsSupportedVersion(version))
throw new InvalidOperationException($"收发包管理器仅适配国际服已审计版本,当前为 {version}。");
Hook<ZoneReceive>? down=null;
Hook<ZoneUp>? up=null;
down=game.Hook<ZoneReceive>(scope,DownSignature,(connection,holder)=>Pipeline.Receive(
()=>down!.Original(connection,holder),accepts=>NativePacketCapture.Down(memory,holder,accepts)));
up=game.Hook<ZoneUp>(scope,UpSignature,(connection,data,arg,flag)=>Pipeline.Send(
()=>up!.Original(connection,data,arg,flag),accepts=>NativePacketCapture.Up(memory,data,accepts)));
scope.Own(ActiveSender.Attach(Pipeline));
try { receiverSend=Marshal.GetDelegateForFunctionPointer<ReceiverSendPacket>(game.Scan(AdaptationManifest.ActiveSendReceiverSignature)); }
catch { receiverSend=null; }
scope.Defer(()=>
{
receiverSend=null;
ActiveSender.CancelAllQueued("收发包管线已关闭,取消待发队列");
});
down.Enable(); up.Enable();
}
// Module owners are released before the manager during Plugin.Dispose.
public void Dispose()
{
if(disposed) return;
disposed=true;
ActiveSender.CancelAllQueued("插件卸载,取消待发队列");
Pipeline.Dispose();
}
}