Files
MSCO-Dedicated-Server/Protocol.cs
T
2026-09-26 20:55:39 +02:00

177 lines
5.6 KiB
C#

using System.Net.Sockets;
namespace MSCO.Server;
/// <summary>Muss mit MsgType in der Mod (NetSession.cs) übereinstimmen.</summary>
public enum MsgType : byte
{
Hello = 1,
Welcome = 2,
Reject = 3,
Player = 4,
SaveBegin = 5,
SaveFile = 6,
StartGame = 7,
ObjStates = 8,
ObjOwn = 9,
ObjRelease = 10,
FsmEvent = 11,
PlayerJoined = 12,
PlayerLeft = 13,
VehicleFx = 14,
Ping = 15,
Ready = 16, // Client -> Server: Spiel geladen, Objekte erfasst
WorldSnapshot = 17,// Server -> Client: aktueller Weltzustand
Info = 18, // Server-Browser fragt Infos ab (ohne Hello)
InfoReply = 19,
WorldTime = 20, // Uhrzeit + Wetter vom "Weltuhr"-Spieler
MoneyDelta = 21, // Geld hat sich geändert (+/- Betrag, gemeinsame Kasse)
DoorState = 22, // Tür/Tor/Haube hat den Zustand gewechselt
AdminStatus = 23, // Server -> Client: bool, ist Admin
AdminListRequest = 24, // Client -> Server: Spielerliste fürs Admin-Menü
AdminList = 25, // Server -> Client: int Anzahl, je [int id, string name, string steamId, bool admin]
AdminAction = 26, // Client -> Server: byte (1 = kick, 2 = ban), int Spieler-ID, string Grund
AdminResult = 27, // Server -> Client: string Meldung
Chat = 28, // Chatnachricht (string)
VarSync = 29 // Fahrzeugzustand: int n, je [int key, byte typ, wert]
}
public readonly record struct Message(MsgType Type, byte[] Payload)
{
public BinaryReader Reader() => new(new MemoryStream(Payload));
}
public static class Wire
{
public static byte[] Build(Action<BinaryWriter> write)
{
using var ms = new MemoryStream();
using var w = new BinaryWriter(ms);
write(w);
w.Flush();
return ms.ToArray();
}
public static byte[] WithSender(int senderId, byte[] payload)
{
var result = new byte[4 + payload.Length];
BitConverter.GetBytes(senderId).CopyTo(result, 0);
Buffer.BlockCopy(payload, 0, result, 4, payload.Length);
return result;
}
public static byte[] Frame(MsgType type, byte[] payload)
{
payload ??= Array.Empty<byte>();
var frame = new byte[5 + payload.Length];
BitConverter.GetBytes(payload.Length + 1).CopyTo(frame, 0);
frame[4] = (byte)type;
Buffer.BlockCopy(payload, 0, frame, 5, payload.Length);
return frame;
}
}
/// <summary>TCP-Verbindung zu einem Spieler, gleiches Format wie in der Mod: [int Länge][byte Typ][Payload].</summary>
public sealed class Connection
{
// Spieler schicken nur kleine Pakete (Positionen, Aktionen); der Spielstand geht nur vom Server zum Spieler
const int MaxFrame = 1024 * 1024;
public bool IsClosed { get; private set; }
public string CloseReason { get; private set; }
public string RemoteAddress { get; }
/// <summary>Wann zuletzt etwas ankam (Clients schicken alle 2 s einen Ping).</summary>
public DateTime LastReceiveUtc { get; private set; } = DateTime.UtcNow;
readonly TcpClient client;
readonly NetworkStream stream;
readonly byte[] readBuffer = new byte[64 * 1024];
byte[] buffer = new byte[64 * 1024];
int count;
public Connection(TcpClient client)
{
this.client = client;
client.NoDelay = true;
client.SendBufferSize = 4 * 1024 * 1024;
stream = client.GetStream();
RemoteAddress = client.Client.RemoteEndPoint?.ToString() ?? "?";
}
public List<Message> Poll()
{
var messages = new List<Message>();
if (IsClosed) return messages;
try
{
if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0)
{
Close("Connection closed");
return messages;
}
while (stream.DataAvailable)
{
int read = stream.Read(readBuffer, 0, readBuffer.Length);
if (read <= 0) break;
Append(readBuffer, read);
LastReceiveUtc = DateTime.UtcNow;
}
Parse(messages);
}
catch (Exception e)
{
Close("Network error: " + e.Message);
}
return messages;
}
public void Send(MsgType type, byte[] payload = null)
{
if (IsClosed) return;
try
{
byte[] frame = Wire.Frame(type, payload);
stream.Write(frame, 0, frame.Length);
}
catch (Exception e)
{
Close("Network error: " + e.Message);
}
}
public void Close(string reason)
{
if (IsClosed) return;
IsClosed = true;
CloseReason = reason;
try { client.Close(); } catch { }
}
void Append(byte[] data, int length)
{
if (count + length > buffer.Length) Array.Resize(ref buffer, Math.Max(count + length, buffer.Length * 2));
Buffer.BlockCopy(data, 0, buffer, count, length);
count += length;
}
void Parse(List<Message> messages)
{
int offset = 0;
while (count - offset >= 5)
{
int length = BitConverter.ToInt32(buffer, offset);
if (length < 1 || length > MaxFrame) throw new IOException("Invalid packet");
if (count - offset - 4 < length) break;
var payload = new byte[length - 1];
Buffer.BlockCopy(buffer, offset + 5, payload, 0, payload.Length);
messages.Add(new Message((MsgType)buffer[offset + 4], payload));
offset += 4 + length;
}
if (offset > 0)
{
Buffer.BlockCopy(buffer, offset, buffer, 0, count - offset);
count -= offset;
}
}
}