104 lines
4.6 KiB
C#
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();
|
|
}
|
|
}
|