using System.Net.Sockets; namespace MSCO.Server; /// Muss mit MsgType in der Mod (NetSession.cs) übereinstimmen. 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] Teleport = 30, // Admin holt einen Spieler zu sich: 3 float Position Pong = 31 // Antwort auf Ping mit demselben Zeitstempel (Laufzeit messen) } 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 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(); 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; } } /// TCP-Verbindung zu einem Spieler, gleiches Format wie in der Mod: [int Länge][byte Typ][Payload]. 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; } /// Wann zuletzt etwas ankam (Clients schicken alle 2 s einen Ping). 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 Poll() { var messages = new List(); 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 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; } } }