Initial import of IC.Rebuild

This commit is contained in:
2026-10-13 22:55:05 +08:00
commit 16d61f7a5e
87 changed files with 17054 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
using System.Diagnostics;
namespace ICRebuild.Core;
public enum NetworkPhase { Received, BeforeSend, AfterSend }
public enum PacketOrigin { NativeZone, PluginActiveSend }
public enum PacketStage { RawIpcPayload }
public sealed record PacketContext(long ObservationId, PacketOrigin Origin, PacketStage Stage,
NetworkPhase Phase, byte? NativeResult = null);
// Owned immutable copy. No native pointer or mutable array is exposed to observers.
public sealed class NetworkMessage
{
private readonly byte[] payload;
public long Id { get; }
public DateTimeOffset Time { get; } = DateTimeOffset.UtcNow;
public long MonotonicTicks { get; } = Stopwatch.GetTimestamp();
public PacketDirection Direction { get; }
public string GameVersion { get; }
public int Opcode { get; }
public uint? Target { get; }
public bool Complete { get; }
public string? Issue { get; }
public PacketOrigin Origin { get; }
public ReadOnlySpan<byte> Payload => payload;
public NetworkMessage(long id, PacketDirection direction, string version, CapturedPacket packet,
PacketOrigin origin = PacketOrigin.NativeZone)
{
Id=id; Direction=direction; GameVersion=version; Opcode=packet.Opcode;
Target=packet.Target; Complete=packet.Complete; Issue=packet.Issue; Origin=origin;
payload=packet.Payload.ToArray();
}
}
public sealed record NetworkObservation(NetworkMessage Message, NetworkPhase Phase, byte? NativeResult = null)
{
public PacketContext Context => new(Message.Id,Message.Origin,PacketStage.RawIpcPayload,Phase,NativeResult);
}
// Inline on the native calling thread, in registration order per observation. Concurrent
// calls may interleave. Callbacks must be short; no game/UI service access, waits for
// Framework, native sending, or changes to native Hook lifetimes from a callback.
public sealed class NetworkPipeline(string version) : IDisposable
{
private sealed class Subscription(NetworkPhase phase, Action<NetworkObservation> callback, int? opcode)
{
public readonly NetworkPhase Phase=phase;
public readonly Action<NetworkObservation> Callback=callback;
public readonly int? Opcode=opcode;
public int Active=1;
public bool Matches(NetworkPhase phase, int? opcode=null) => Volatile.Read(ref Active)!=0 &&
Phase==phase && (!opcode.HasValue || !Opcode.HasValue || Opcode==opcode);
}
private readonly object sync=new();
private Subscription[] subscriptions=[];
private bool disposed;
private long sequence,readErrors,observerErrors,received,beforeSend,afterSend;
private int lastSendResult=-1;
private string lastIssue="无";
public long ReadErrors => Interlocked.Read(ref readErrors);
public long ObserverErrors => Interlocked.Read(ref observerErrors);
public long Received => Interlocked.Read(ref received);
public long BeforeSend => Interlocked.Read(ref beforeSend);
public long AfterSend => Interlocked.Read(ref afterSend);
public int LastSendResult => Volatile.Read(ref lastSendResult);
public string LastIssue => Volatile.Read(ref lastIssue);
public int SubscriberCount => Volatile.Read(ref subscriptions).Length;
public IDisposable Subscribe(NetworkPhase phase, Action<NetworkObservation> callback, int? opcode=null)
{
ArgumentNullException.ThrowIfNull(callback);
if(!Enum.IsDefined(phase) || opcode is <0 or >65535) throw new ArgumentOutOfRangeException(nameof(opcode));
var item=new Subscription(phase,callback,opcode);
lock(sync)
{
ObjectDisposedException.ThrowIf(disposed,this);
Volatile.Write(ref subscriptions,[..subscriptions,item]);
}
return new ActionDisposable(()=>
{
// Already-entered callbacks may finish; cancellation cannot recall them.
Interlocked.Exchange(ref item.Active,0);
lock(sync) Volatile.Write(ref subscriptions,subscriptions.Where(x=>x!=item).ToArray());
});
}
public byte Send(Func<byte> original, Func<Func<int,bool>,CaptureRead> capture)
{
var listeners=Volatile.Read(ref subscriptions);
NetworkMessage? message=null;
if(listeners.Any(s=>s.Matches(NetworkPhase.BeforeSend) || s.Matches(NetworkPhase.AfterSend)))
message=Read(PacketDirection.Up,capture,opcode=>listeners.Any(s=>
s.Matches(NetworkPhase.BeforeSend,opcode) || s.Matches(NetworkPhase.AfterSend,opcode)));
if(message!=null)
{
Interlocked.Increment(ref beforeSend);
Publish(listeners,new(message,NetworkPhase.BeforeSend));
}
// Outside observer/read exception handlers: never retry the native call.
var result=original();
Volatile.Write(ref lastSendResult,result);
if(message!=null)
{
Interlocked.Increment(ref afterSend);
Publish(listeners,new(message,NetworkPhase.AfterSend,result));
}
return result;
}
public byte Receive(Func<byte> original, Func<Func<int,bool>,CaptureRead> capture)
{
var result=original();
if(result==0) return result; // Holder is unreadable on unsuccessful receive.
var listeners=Volatile.Read(ref subscriptions);
if(listeners.Any(s=>s.Matches(NetworkPhase.Received)))
{
var message=Read(PacketDirection.Down,capture,opcode=>listeners.Any(s=>s.Matches(NetworkPhase.Received,opcode)));
if(message!=null)
{
Interlocked.Increment(ref received);
Publish(listeners,new(message,NetworkPhase.Received,result));
}
}
return result;
}
private NetworkMessage? Read(PacketDirection direction,Func<Func<int,bool>,CaptureRead> capture,Func<int,bool> accepts)
{
try
{
var read=capture(accepts);
if(read.Error!=null) { ReadFailed(read.Error); return null; }
if(read.Packet is not {} packet) return null;
if(!packet.Complete) ReadFailed(packet.Issue ?? "载荷读取不完整");
var origin=direction==PacketDirection.Up && ActiveSendScope.Active ? PacketOrigin.PluginActiveSend : PacketOrigin.NativeZone;
return new(Interlocked.Increment(ref sequence),direction,version,packet,origin);
}
catch(Exception ex) { ReadFailed(ex.Message); return null; }
}
private void ReadFailed(string issue)
{
Interlocked.Increment(ref readErrors); Volatile.Write(ref lastIssue,issue);
}
[ThreadStatic] private static int callbackDepth;
public static bool InCallback => callbackDepth > 0;
private void Publish(Subscription[] listeners,NetworkObservation observation)
{
callbackDepth++;
try
{
foreach(var listener in listeners)
{
if(!listener.Matches(observation.Phase,observation.Message.Opcode)) continue;
try { listener.Callback(observation); }
catch(Exception ex)
{
Interlocked.Increment(ref observerErrors);
Volatile.Write(ref lastIssue,$"{observation.Phase} 订阅异常:{ex.Message}");
}
}
}
finally
{
if(callbackDepth>0) callbackDepth--;
}
}
public void Dispose()
{
lock(sync)
{
disposed=true;
foreach(var listener in subscriptions) Interlocked.Exchange(ref listener.Active,0);
Volatile.Write(ref subscriptions,[]);
}
}
}