RaiNote.PacketMediator
0.0.7
dotnet add package RaiNote.PacketMediator --version 0.0.7
NuGet\Install-Package RaiNote.PacketMediator -Version 0.0.7
<PackageReference Include="RaiNote.PacketMediator" Version="0.0.7" />
paket add RaiNote.PacketMediator --version 0.0.7
#r "nuget: RaiNote.PacketMediator, 0.0.7"
// Install RaiNote.PacketMediator as a Cake Addin #addin nuget:?package=RaiNote.PacketMediator&version=0.0.7 // Install RaiNote.PacketMediator as a Cake Tool #tool nuget:?package=RaiNote.PacketMediator&version=0.0.7
About
This project is meant as a simple-enough API to create different network packet definitions, which are required to be serialized and deserialized in a high throughput environment. The primary user audience would be for server emulation for old software, specifically games, that have been abandoned or simply canceled together. Rai.PacketMediator was formerly a part of a larger side-project to create a server emulator in a more modern .NET manner than older projects for an old 2D side-scrolling mmorpg called Wonderking.
Caveats
It does not and probably will not support ahead-of-time compilation for a long time, as to why it can be inferred by reading the "how it works" section. Furthermore, with how the current API is designed, there will be lots of reoccurring text components specifically looking at how packet handlers are implemented.
How it works
This library is using DotNext to generate on startup the required methods for packet handling and passing around of packets. That is the primary reason why it will not support ahead-of-time compilation as custom IL is emitted.
It will primarily create a contract that needs to be followed for packet definitions and their respective handlers.
Samples
Minimal setup using NetCoreServer for TCPSession and MassTransit for Consumption While this, so to say, minimal sample is already rather long as there are lots of parts that need to be considered when bootstrapping this library. It should give a general overview on how interact and a possible solution as to how to transfer packets between a session and a handler.
// OperationCode.cs
public enum OperationCode : ushort
{
LoginRequest = 1
}
// LoginRequestPacket.cs
[GamePacketIdAttribute(OperationCode.LoginRequest)]
public class LoginRequestPacket : IIncomingPacket
{
public required string Username { get; set; }
public required string Password { get; set; }
public void Deserialize(byte[] data)
{
Username = Encoding.ASCII.GetString(data, 0, 32)
Password = Encoding.ASCII.GetString(data, 32, 64)
}
}
// GamePacketIdAttribute.cs
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class GamePacketIdAttribute : PacketIdAttribute<OperationCode>
{
public GamePacketIdAttribute(OperationCode code) : base(code)
{
}
}
// LoginHandler.cs
public class LoginHandler : IPacketHandler<LoginRequest, AuthSession>
{
private readonly IConfiguration _configuration;
private readonly ILogger<LoginHandler> _logger;
private readonly DbContext _dbContext;
public LoginHandler(ILogger<LoginHandler> logger, DbContext dbContext, IConfiguration configuration)
{
_logger = logger;
_dbContext = dbContext;
_configuration = configuration;
}
public async Task HandleAsync(LoginInfoPacket packet, AuthSession session, CancellationToken cancellationToken)
{
// Insert logic here
_ = session.SendAsync(new byte[]{});
}
}
// Packetconsumer.cs
public class PacketConsumer : IConsumer<RawPacket>
{
private readonly PacketDistributorService<OperationCode, AuthSession> _distributorService;
public PacketConsumer(PacketDistributorService<OperationCode, AuthSession> distributorService)
{
_distributorService = distributorService;
}
public Task Consume(ConsumeContext<RawPacket> context)
{
return _distributorService.AddPacketAsync(context.Message.MessageBody, context.Message.OperationCode,
context.Message.Session);
}
}
// RawPacket.cs
[MessageUrn("packets")]
public class RawPacket
{
public RawPacket(OperationCode operationCode, Span<byte> messageBody, Guid sessionId, AuthSession session)
{
MessageBody = messageBody.ToArray();
SessionId = sessionId;
Session = session;
OperationCode = operationCode;
}
public OperationCode OperationCode { get; }
public byte[] MessageBody { get; }
public Guid SessionId { get; }
public AuthSession Session { get; }
}
// AuthSession.cs
public class AuthSession : TcpSession
{
private readonly PacketDistributorService<OperationCode, AuthSession> _distributorService;
private readonly ILogger<AuthSession> _logger;
private readonly IMediator _mediator;
public AuthSession(TcpServer
server, IMediator mediator, ILogger<AuthSession> logger,
PacketDistributorService<OperationCode, AuthSession> distributorService) : base(server)
{
_mediator = mediator;
_logger = logger;
_distributorService = distributorService;
}
public Guid AccountId { get; set; }
public Task SendAsync(IOutgoingPacket packet)
{
var opcode = _distributorService.GetOperationCodeByPacketType(packet);
Span<byte> packetData = packet.Serialize();
var length = (ushort)(packetData.Length + 2);
Span<byte> buffer = stackalloc byte[length];
buffer.Clear();
packetData.CopyTo(buffer[2..length]);
var bytesOfOpcode = BitConverter.GetBytes((ushort)opcode);
for (var i = 0; i < bytesOfOpcode.Length || i < 2; i++)
{
buffer[2 + i] = bytesOfOpcode[i];
}
SendAsync(buffer);
return Task.CompletedTask;
}
protected override void OnReceived(byte[] buffer, long offset, long size)
{
Span<byte> decryptedBuffer = stackalloc byte[(int)size];
var dataBuffer = Decrypt(buffer.AsSpan(2, (int)size - 2));
var opCode = BitConverter.ToUInt16(buffer.ToArray(), 0);
var rawPacket = new RawPacket((OperationCode)opCode, dataBuffer, Id, this);
_ = _mediator.Send(rawPacket);
base.OnReceived(decryptedBuffer.ToArray(), offset, decryptedBuffer.Length);
}
}
// Program.cs
// create builder
builder.Services.AddSingleton(provider =>
new PacketDistributorService<OperationCode, AuthSession>(
provider.GetRequiredService<IServiceProvider>(),
new List<Assembly> { Assembly.GetAssembly(typeof(OperationCode)) }.AsReadOnly(),
new List<Assembly> { Assembly.GetAssembly(typeof(LoginHandler)) }.AsReadOnly()
));
builder.Services.AddHostedService(provider =>
provider.GetService<PacketDistributorService<OperationCode, AuthSession>>() ??
throw new InvalidOperationException());
// build services
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- DotNext (>= 5.3.1)
- DotNext.Metaprogramming (>= 5.3.0)
- JetBrains.Annotations (>= 2023.3.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.