3 using System.Collections.Generic;
7 internal sealed
class SteamP2PServerPeer : ServerPeer
11 private readonly SteamId ownerSteamId;
13 private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
15 private SteamId ReadSteamId(IReadMessage inc) =>
new SteamId(inc.ReadUInt64() ^ ownerKey64);
16 private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
18 public SteamP2PServerPeer(SteamId steamId,
int ownerKey, ServerSettings settings, Callbacks callbacks) : base(callbacks)
20 serverSettings = settings;
22 connectedClients =
new List<NetworkConnection>();
23 pendingClients =
new List<PendingClient>();
25 this.ownerKey = Option<int>.Some(ownerKey);
27 ownerSteamId = steamId;
32 public override void Start()
34 var headers =
new PeerPacketHeaders
40 SendMsgInternal(ownerSteamId, headers,
null);
45 public override void Close()
47 if (!started) {
return; }
51 for (
int i = pendingClients.Count - 1; i >= 0; i--)
53 RemovePendingClient(pendingClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
56 for (
int i = connectedClients.Count - 1; i >= 0; i--)
58 Disconnect(connectedClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
61 pendingClients.Clear();
62 connectedClients.Clear();
64 ChildServerRelay.ShutDown();
66 callbacks.OnShutdown.Invoke();
69 public override void Update(
float deltaTime)
71 if (!started) {
return; }
74 for (
int i = connectedClients.Count - 1; i >= 0; i--)
76 SteamP2PConnection conn = (SteamP2PConnection)connectedClients[i];
77 conn.Decay(deltaTime);
78 if (conn.Timeout < 0.0)
80 Disconnect(conn, PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
86 while (ChildServerRelay.Read(out
byte[] incBuf))
88 IReadMessage inc =
new ReadOnlyMessage(incBuf,
false, 0, incBuf.Length, OwnerConnection);
90 HandleDataMessage(inc);
96 string errorMsg =
"Server failed to read an incoming message. {" + e +
"}\n" + e.StackTrace.CleanupStackTrace();
97 GameAnalyticsManager.AddErrorEventOnce($
"SteamP2PServerPeer.Update:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
99 DebugConsole.ThrowError(errorMsg);
101 if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
105 for (
int i = 0; i < pendingClients.Count; i++)
107 PendingClient pendingClient = pendingClients[i];
108 UpdatePendingClient(pendingClient);
109 if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
113 private void HandleDataMessage(IReadMessage inc)
115 if (!started) {
return; }
117 SteamId senderSteamId = ReadSteamId(inc);
118 SteamId sentOwnerSteamId = ReadSteamId(inc);
120 var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
122 if (packetHeader.IsServerMessage())
124 DebugConsole.ThrowError($
"Got server message from {senderSteamId}");
128 if (senderSteamId != ownerSteamId)
130 bool connectionMatches(NetworkConnection conn) =>
131 conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var steamId } }
132 && steamId == senderSteamId;
134 PendingClient? pendingClient = pendingClients.Find(c => connectionMatches(c.Connection));
135 SteamP2PConnection? connectedClient = connectedClients.Find(connectionMatches) as SteamP2PConnection;
137 pendingClient?.Heartbeat();
138 connectedClient?.Heartbeat();
140 if (packetHeader.IsConnectionInitializationStep())
142 if (!initialization.HasValue) {
return; }
145 if (pendingClient !=
null)
147 pendingClient.Connection.SetAccountInfo(
new AccountInfo(senderSteamId, sentOwnerSteamId));
148 ReadConnectionInitializationStep(
150 new ReadWriteMessage(inc.Buffer, inc.BitPosition, inc.LengthBits,
false),
155 pendingClient =
new PendingClient(
new SteamP2PConnection(senderSteamId));
156 pendingClient.Connection.SetAccountInfo(
new AccountInfo(senderSteamId, sentOwnerSteamId));
157 pendingClients.Add(pendingClient);
160 else if (serverSettings.BanList.IsBanned(senderSteamId, out
string banReason) ||
161 serverSettings.BanList.IsBanned(sentOwnerSteamId, out banReason))
163 if (pendingClient !=
null)
165 RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banReason));
167 else if (connectedClient !=
null)
169 Disconnect(connectedClient, PeerDisconnectPacket.Banned(banReason));
172 else if (packetHeader.IsDisconnectMessage())
174 if (pendingClient !=
null)
176 RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
178 else if (connectedClient !=
null)
180 Disconnect(connectedClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
183 else if (packetHeader.IsHeartbeatMessage())
188 else if (connectedClient !=
null)
190 var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
191 IReadMessage msg =
new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, connectedClient);
192 callbacks.OnMessageReceived.Invoke(connectedClient, msg);
197 (OwnerConnection as SteamP2PConnection)?.Heartbeat();
199 if (packetHeader.IsDisconnectMessage())
201 DebugConsole.ThrowError(
"Received disconnect message from owner");
205 if (packetHeader.IsServerMessage())
207 DebugConsole.ThrowError(
"Received server message from owner");
211 if (packetHeader.IsConnectionInitializationStep())
213 if (OwnerConnection is
null)
215 var packet = INetSerializableStruct.Read<SteamP2PInitializationOwnerPacket>(inc);
216 OwnerConnection =
new SteamP2PConnection(ownerSteamId)
218 Language = GameSettings.CurrentConfig.Language
220 OwnerConnection.SetAccountInfo(
new AccountInfo(ownerSteamId, ownerSteamId));
222 callbacks.OnInitializationComplete.Invoke(OwnerConnection, packet.OwnerName);
223 callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
229 if (packetHeader.IsHeartbeatMessage())
235 var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
236 IReadMessage msg =
new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, OwnerConnection);
237 callbacks.OnMessageReceived.Invoke(OwnerConnection!, msg);
242 public override void InitializeSteamServerCallbacks()
244 throw new InvalidOperationException(
"Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
247 public override void Send(IWriteMessage msg, NetworkConnection conn,
DeliveryMethod deliveryMethod,
bool compressPastThreshold =
true)
249 if (!started) {
return; }
251 if (conn is not SteamP2PConnection steamP2PConn) {
return; }
253 if (!connectedClients.Contains(steamP2PConn) && conn != OwnerConnection)
255 DebugConsole.ThrowError($
"Tried to send message to unauthenticated connection: {steamP2PConn.AccountInfo.AccountId}");
259 if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || connAccountId is not SteamId) {
return; }
261 byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out
bool isCompressed, out _);
263 var headers =
new PeerPacketHeaders
268 Initialization =
null
270 var body =
new PeerPacketMessage
274 SendMsgInternal(steamP2PConn, headers, body);
277 private void SendDisconnectMessage(SteamId steamId, PeerDisconnectPacket peerDisconnectPacket)
279 if (!started) {
return; }
281 var headers =
new PeerPacketHeaders
285 Initialization =
null
288 SendMsgInternal(steamId, headers, peerDisconnectPacket);
291 public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
293 if (!started) {
return; }
295 if (conn is not SteamP2PConnection steamp2pConn) {
return; }
297 if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || connAccountId is not SteamId connSteamId) {
return; }
299 SendDisconnectMessage(connSteamId, peerDisconnectPacket);
301 if (connectedClients.Contains(steamp2pConn))
304 connectedClients.Remove(steamp2pConn);
305 callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket);
306 Steam.SteamManager.StopAuthSession(connSteamId);
308 else if (steamp2pConn == OwnerConnection)
310 throw new InvalidOperationException(
"Cannot disconnect owner peer");
314 protected override void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
316 var connSteamId = conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var
id } } ? id :
null;
317 if (connSteamId is
null) {
return; }
319 SendMsgInternal(connSteamId, headers, body);
322 private void SendMsgInternal(SteamId connSteamId, PeerPacketHeaders headers, INetSerializableStruct? body)
324 IWriteMessage msgToSend =
new WriteOnlyMessage();
325 WriteSteamId(msgToSend, connSteamId);
326 msgToSend.WriteNetSerializableStruct(headers);
327 body?.Write(msgToSend);
329 ForwardToOwnerProcess(msgToSend);
332 private static void ForwardToOwnerProcess(IWriteMessage msg)
334 byte[] bufToSend = (
byte[])msg.Buffer.Clone();
335 Array.Resize(ref bufToSend, msg.LengthBytes);
336 ChildServerRelay.Write(bufToSend);
339 protected override void ProcessAuthTicket(ClientSteamTicketAndVersionPacket packet, PendingClient pendingClient)
342 pendingClient.Name = packet.Name;
343 pendingClient.AuthSessionStarted =
true;