NuGet package: ProtankiNetworking on NuGet
A C# library for ProTanki game communication. This project is based on code from the ProboTanki-Lib Python library, but it is not exact port.
- ProtankiProxy uses this networking library in a real-world proxy setup.
- It can also be very useful when debugging this library, especially for inspecting packet flow and validating encode/decode behavior.
The library provides three main components for TCP networking:
TankiTcpListener: Base class for TCP server implementation that accepts client connectionsTankiTcpClientHandler: Base class for handling individual client connections to TankiTcpListenerTankiTcpClient: Base class for TCP client implementation
public class MyTankiServer : TankiTcpListener
{
public MyTankiServer(IPEndPoint localEndPoint)
: base(localEndPoint)
{
}
protected override TankiTcpClientHandler CreateClientHandler(
TcpClient client,
CancellationToken cancellationToken)
{
return new MyClientHandler(client, new Protection(), cancellationToken);
}
protected override async Task OnErrorAsync(Exception exception, string context)
{
Console.WriteLine($"Server error in {context}: {exception.Message}");
}
protected override Task OnClientConnectedAsync(TcpClient client)
{
return Task.CompletedTask;
}
protected override Task OnClientDisconnectedAsync(TcpClient client)
{
return Task.CompletedTask;
}
}
// Usage:
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
var server = new MyTankiServer(endPoint);
server.Start();public class MyClientHandler : TankiTcpClientHandler
{
public MyClientHandler(
TcpClient client,
Protection protection,
CancellationToken cancellationToken)
: base(client, protection, cancellationToken)
{
}
protected override async Task OnRawPacketReceivedAsync(byte[] rawPacket)
{
Console.WriteLine($"Received raw packet of length {rawPacket.Length}");
}
protected override async Task OnPacketReceivedAsync(Packet packet)
{
Console.WriteLine($"Received packet of type {packet.GetType().Name}");
}
protected override async Task OnErrorAsync(Exception exception, string context)
{
Console.WriteLine($"Handler error in {context}: {exception.Message}");
}
protected override Task OnConnectedAsync()
{
return Task.CompletedTask;
}
protected override Task OnDisconnectedAsync()
{
return Task.CompletedTask;
}
protected override Task OnPacketUnwrapFailureAsync(Type packetType, int packetId, Exception exception)
{
Console.WriteLine($"Unwrap failed for {packetType.Name} ({packetId}): {exception.Message}");
return Task.CompletedTask;
}
}public class MyTankiClient : TankiTcpClient
{
public MyTankiClient(IPEndPoint serverEndPoint, Protection protection)
: base(serverEndPoint, protection)
{
}
protected override async Task OnRawPacketReceivedAsync(byte[] rawPacket)
{
Console.WriteLine($"Received raw packet of length {rawPacket.Length}");
}
protected override async Task OnPacketReceivedAsync(Packet packet)
{
Console.WriteLine($"Received packet of type {packet.GetType().Name}");
}
protected override async Task OnErrorAsync(Exception exception, string context)
{
Console.WriteLine($"Client error in {context}: {exception.Message}");
}
protected override Task OnConnectedAsync()
{
return Task.CompletedTask;
}
protected override Task OnDisconnectedAsync()
{
return Task.CompletedTask;
}
protected override Task OnPacketUnwrapFailureAsync(Type packetType, int packetId, Exception exception)
{
Console.WriteLine($"Unwrap failed for {packetType.Name} ({packetId}): {exception.Message}");
return Task.CompletedTask;
}
}
// Usage:
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
var protection = new Protection(); // Configure protection as needed
var client = new MyTankiClient(endPoint, protection);
await client.ConnectAsync();Code/Codec/- Data encoding/decoding systemCode/Networking/- Network communication utilitiesCode/Packets/- Game packet definitionsCode/Security/- Security and protection mechanisms
Packet direction is named from the point of view of your app using this library:
In...Packet= packet comes in to your app (server -> client)Out...Packet= packet goes out from your app (client -> server)
Examples:
LoginSuccessInPacketis received by the client from server.LoginOutPacketis sent by the client to server.
Every packet class inherits from Packet and usually defines:
ID_CONST(the packet id constant)Idoverride returningID_CONSTDescriptionoverride- fields/properties marked with
[Encode(order)]for payload serialization
Minimal example:
public class LoginOutPacket : Packet
{
[Encode(0)] public string? Username { get; set; }
[Encode(1)] public string? Password { get; set; }
[Encode(2)] public bool RememberMe { get; set; }
public const int ID_CONST = -739684591;
public override int Id => ID_CONST;
}- Only members with
[Encode(...)]are serialized/deserialized. orderdefines exact write/read order in payload.
PacketManagermapspacketId-> packet type using reflection.- If id is unknown, or decoding fails, library falls back to
UnknownPacket. RawDatakeeps original full bytes (header + payload).DecryptedDatakeeps decrypted payload bytes only.
Every encodable data class implements IEncodable and usually defines:
IsOptionalflag for nullable object encoding metadata.IsArrayOptionalflag for nullable array/vector encoding metadata.- fields/properties marked with
[Encode(order)]for payload serialization.
Minimal example:
public class AssaultCC : IEncodable
{
public bool IsOptional { get; } = false;
public bool IsArrayOptional { get; } = false;
[Encode(0)] public ClientAssaultFlag?[]? BlueFlags { get; set; }
[Encode(1)] public Resource? FlagPedestalModel { get; set; }
[Encode(2)] public Resource? FlagSprite { get; set; }
}- Only members with
[Encode(...)]are serialized/deserialized. orderdefines exact write/read order in payload.
IsOptional = trueadds a leading boolean marker indicating whether the object isnull.IsArrayOptional = trueadds a leading boolean marker when this type is used in arrays/vectors.
This library is for educational purposes only. Use of this library should comply with ProTanki's terms of service. (so you can't use it at all :D)