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 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 nativeBuffer) { var sender=receiverSend; if(sender==null || nativeBuffer.LengthNetworkModuleProxy==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? down=null; Hook? up=null; down=game.Hook(scope,DownSignature,(connection,holder)=>Pipeline.Receive( ()=>down!.Original(connection,holder),accepts=>NativePacketCapture.Down(memory,holder,accepts))); up=game.Hook(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(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(); } }