This is the repository for the nuget package Rai.PacketMediator.
Go to file
2024-04-04 17:24:38 +02:00
.gitea/workflows chore: weird issue 2024-04-04 17:24:38 +02:00
PacketMediator.Samples chore: nuget adjustments for packet ID 2024-04-04 16:32:04 +02:00
PacketMediator.Tests chore: nuget adjustments for packet ID 2024-04-04 16:32:04 +02:00
RaiNote.PacketMediator chore: nuget improvements 2024-04-04 17:21:43 +02:00
.editorconfig chore: new Projects and fixed naming 2024-03-02 12:49:45 +01:00
.gitignore chore: Initial commit + v0.0.1 2024-02-08 13:24:28 +01:00
.pre-commit-config.yaml chore: reformatting 2024-02-26 11:32:45 +01:00
global.json chore(deps): update dependency dotnet-sdk to v8.0.203 2024-03-15 07:00:03 +00:00
LICENSE chore: License changed to Apache 2.0 2024-02-26 11:33:15 +01:00
PacketMediator.sln chore: nuget adjustments for packet ID 2024-04-04 16:32:04 +02:00
projects_ignore_licenses.json chore: Initial commit + v0.0.1 2024-02-08 13:24:28 +01:00
README.md chore: README added 2024-02-26 13:17:21 +01:00
renovate.json chore: renovate configuration 2024-02-26 12:40:11 +01:00

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