Add C# dedicated server replacement

This commit is contained in:
Nomads_Reach
2026-08-16 02:23:04 -04:00
parent 0024436ce8
commit ddadf87423
17 changed files with 3516 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
using System.Net.Sockets;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace CommonwealthOnline.Server;
internal sealed class SyntheticProtocolClient : IAsyncDisposable
{
private readonly TcpClient _client = new();
private NetworkStream? _stream;
private readonly MemoryStream _buffer = new();
public uint PlayerId { get; private set; }
public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken = default)
{
await _client.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
_client.NoDelay = true;
_stream = _client.GetStream();
var welcome = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
if (JsonHelpers.String(welcome["type"]) != "welcome") throw new InvalidDataException("Server did not send welcome packet.");
await SendAsync(new JsonObject { ["type"] = "hello", ["protocolVersion"] = ProtocolConstants.ProtocolVersion }, cancellationToken).ConfigureAwait(false);
while (true)
{
var packet = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
if (JsonHelpers.String(packet["type"]) != "sessionReady") continue;
if (!JsonHelpers.TryUInt32(packet["playerId"], 1, uint.MaxValue, out var id)) throw new InvalidDataException("sessionReady did not contain a valid playerId.");
PlayerId = id;
break;
}
}
public async Task SendAsync(JsonObject packet, CancellationToken cancellationToken = default)
{
if (_stream is null) throw new InvalidOperationException("Client is not connected.");
var encoded = JsonSerializer.SerializeToUtf8Bytes(packet);
if (encoded.Length > ProtocolConstants.MaxMessageBytes) throw new InvalidDataException("Synthetic packet exceeds maximum message size.");
await _stream.WriteAsync(encoded, cancellationToken).ConfigureAwait(false);
await _stream.WriteAsync(new byte[] { (byte)'\n' }, cancellationToken).ConfigureAwait(false);
}
public async Task<JsonObject> ReceiveAsync(CancellationToken cancellationToken = default)
{
if (_stream is null) throw new InvalidOperationException("Client is not connected.");
var one = new byte[1];
_buffer.SetLength(0);
while (_buffer.Length <= ProtocolConstants.MaxMessageBytes)
{
var count = await _stream.ReadAsync(one, cancellationToken).ConfigureAwait(false);
if (count == 0) throw new EndOfStreamException("Server closed the connection.");
if (one[0] == (byte)'\n')
{
var data = _buffer.ToArray();
if (data.Length > 0 && data[^1] == (byte)'\r') Array.Resize(ref data, data.Length - 1);
return JsonNode.Parse(data) as JsonObject ?? throw new InvalidDataException("Server message was not a JSON object.");
}
_buffer.WriteByte(one[0]);
}
throw new InvalidDataException("Server message exceeded maximum size.");
}
public Task SendTransformAsync(double x, double y, double z, string cellId, string worldspaceId = "", string movementType = "normal", CancellationToken cancellationToken = default) =>
SendAsync(new JsonObject
{
["type"] = "transform", ["x"] = x, ["y"] = y, ["z"] = z, ["angleZ"] = 0.0,
["cellId"] = cellId, ["worldspaceId"] = worldspaceId, ["movementType"] = movementType
}, cancellationToken);
public async ValueTask DisposeAsync()
{
try { _stream?.Dispose(); } catch { }
try { _client.Dispose(); } catch { }
_buffer.Dispose();
await ValueTask.CompletedTask;
}
}