using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using UnityEngine; namespace MSCO { public enum MsgType : byte { Hello = 1, // Client -> Host: Mod-Version Welcome = 2, // Host -> Client: Handshake ok Reject = 3, // Host -> Client: Grund Player = 4, // Spielerposition SaveBegin = 5, // Host -> Client: Spielstand kommt SaveFile = 6, // Host -> Client: Dateiname + Inhalt StartGame = 7, // Host -> Client: jetzt laden } public struct Message { public MsgType Type; public byte[] Payload; public BinaryReader Reader() { return new BinaryReader(new MemoryStream(Payload)); } } public struct PlayerState { public Vector3 Position; public float RotationY; public byte[] Serialize() { Vector3 pos = Position; float rotY = RotationY; return NetSession.Build(w => { w.Write(pos.x); w.Write(pos.y); w.Write(pos.z); w.Write(rotY); }); } public static PlayerState Deserialize(BinaryReader r) { return new PlayerState { Position = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()), RotationY = r.ReadSingle() }; } } /// /// TCP-Verbindung zu genau einem Mitspieler. Nachrichten: [int Länge][byte Typ][Payload]. /// Lesen ist nicht-blockierend und wird jedes Frame über Poll() abgefragt. /// public class NetSession { const int MaxFrame = 32 * 1024 * 1024; public bool IsHost { get; private set; } public bool IsConnected { get { return stream != null; } } public bool IsClosed { get; private set; } public string CloseReason { get; private set; } TcpListener listener; TcpClient client; NetworkStream stream; IAsyncResult connecting; bool justConnected; byte[] buffer = new byte[64 * 1024]; int bufferCount; public static NetSession StartHost(int port) { var s = new NetSession { IsHost = true }; s.listener = new TcpListener(IPAddress.Any, port); s.listener.Start(); return s; } public static NetSession StartClient(string ip, int port) { var s = new NetSession(); s.client = new TcpClient(); s.connecting = s.client.BeginConnect(ip, port, null, null); return s; } /// True genau einmal, direkt nachdem die Verbindung steht. public bool ConsumeConnected() { bool c = justConnected; justConnected = false; return c; } public List Poll() { var messages = new List(); if (IsClosed) return messages; try { AcceptOrConnect(); if (stream == null) return messages; // Verbindung von der Gegenseite geschlossen? if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0) { Close("Connection closed by other player"); return messages; } while (stream.DataAvailable) { EnsureCapacity(bufferCount + client.Available); int read = stream.Read(buffer, bufferCount, buffer.Length - bufferCount); if (read <= 0) break; bufferCount += read; } ParseFrames(messages); } catch (Exception e) { Close("Network error: " + e.Message); } return messages; } void AcceptOrConnect() { if (IsHost) { while (listener.Pending()) { TcpClient incoming = listener.AcceptTcpClient(); if (client != null) { incoming.Close(); continue; } // nur ein Mitspieler client = incoming; OnSocketReady(); } } else if (connecting != null && connecting.IsCompleted) { IAsyncResult result = connecting; connecting = null; client.EndConnect(result); // wirft, wenn nicht erreichbar OnSocketReady(); } } void OnSocketReady() { client.NoDelay = true; client.SendBufferSize = 4 * 1024 * 1024; stream = client.GetStream(); justConnected = true; } void ParseFrames(List messages) { int offset = 0; while (bufferCount - offset >= 5) { int length = BitConverter.ToInt32(buffer, offset); if (length < 1 || length > MaxFrame) throw new IOException("Invalid packet"); if (bufferCount - offset - 4 < length) break; var payload = new byte[length - 1]; Buffer.BlockCopy(buffer, offset + 5, payload, 0, payload.Length); messages.Add(new Message { Type = (MsgType)buffer[offset + 4], Payload = payload }); offset += 4 + length; } if (offset > 0) { Buffer.BlockCopy(buffer, offset, buffer, 0, bufferCount - offset); bufferCount -= offset; } } void EnsureCapacity(int needed) { if (needed <= buffer.Length) return; var bigger = new byte[Math.Max(needed, buffer.Length * 2)]; Buffer.BlockCopy(buffer, 0, bigger, 0, bufferCount); buffer = bigger; } public void Send(MsgType type, byte[] payload = null) { if (stream == null) return; if (payload == null) payload = new byte[0]; try { 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); stream.Write(frame, 0, frame.Length); // blockierend, damit große Dateien komplett rausgehen } catch (Exception e) { Close("Network error: " + e.Message); } } 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 void Close(string reason = "Disconnected") { if (IsClosed) return; IsClosed = true; CloseReason = reason; stream = null; try { if (client != null) client.Close(); } catch { } try { if (listener != null) listener.Stop(); } catch { } } } }