4 using System.Collections.Generic;
5 using System.Threading;
12 private readonly
SteamId selfSteamID;
13 private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
18 private long sentBytes, receivedBytes;
20 private sealed
class RemotePeer
24 public double? DisconnectTime;
25 public bool Authenticating;
26 public bool Authenticated;
42 public readonly List<UnauthedMessage> UnauthedMessages;
44 public RemotePeer(
SteamId steamId)
48 DisconnectTime =
null;
49 Authenticating =
false;
50 Authenticated =
false;
52 UnauthedMessages =
new List<UnauthedMessage>();
56 private List<RemotePeer> remotePeers =
null!;
60 ServerConnection =
null;
64 selfSteamID = SteamManager.GetSteamId().TryUnwrap(out var steamId)
66 :
throw new InvalidOperationException(
"Steamworks not initialized");
72 if (isActive) {
return; }
81 remotePeers =
new List<RemotePeer>();
83 Steamworks.SteamNetworking.ResetActions();
84 Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection;
85 Steamworks.SteamUser.OnValidateAuthTicketResponse += OnAuthChange;
87 Steamworks.SteamNetworking.AllowP2PPacketRelay(
true);
92 private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
94 RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
96 if (remotePeer ==
null) {
return; }
98 if (status == Steamworks.AuthResponse.OK)
100 if (remotePeer.Authenticated) {
return; }
102 SteamId ownerSteamId =
new SteamId(ownerId);
103 remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
104 remotePeer.Authenticated =
true;
105 remotePeer.Authenticating =
false;
106 foreach (var unauthedMessage
in remotePeer.UnauthedMessages)
108 IWriteMessage msg =
new WriteOnlyMessage();
109 WriteSteamId(msg, unauthedMessage.Sender);
110 WriteSteamId(msg, ownerSteamId);
111 msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.Length);
112 ForwardToServerProcess(msg);
115 remotePeer.UnauthedMessages.Clear();
119 DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(status));
123 private void OnIncomingConnection(Steamworks.SteamId steamId)
125 if (!isActive) {
return; }
127 if (remotePeers.None(p => p.SteamId.Value == steamId))
129 remotePeers.Add(
new RemotePeer(
new SteamId(steamId)));
132 Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
135 private void OnP2PData(ulong steamId, IReadMessage inc)
137 if (!isActive) {
return; }
139 RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
140 if (remotePeer ==
null) {
return; }
142 if (remotePeer.DisconnectTime !=
null) {
return; }
146 ProcessP2PData(steamId, remotePeer, inc);
150 string errorMsg = $
"Server failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
151 GameAnalyticsManager.AddErrorEventOnce($
"SteamP2POwnerPeer.OnP2PData:OwnerReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
153 DebugConsole.ThrowError(errorMsg);
155 if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
160 private void ProcessP2PData(ulong steamId, RemotePeer remotePeer, IReadMessage inc)
162 var (deliveryMethod, packetHeader, connectionInitialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
164 if (remotePeer is { Authenticated:
false, Authenticating:
false } && packetHeader.IsConnectionInitializationStep())
166 remotePeer.DisconnectTime =
null;
168 ConnectionInitialization initialization = connectionInitialization ??
throw new Exception(
"Initialization step missing");
171 remotePeer.Authenticating =
true;
173 var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
175 packet.SteamAuthTicket.TryUnwrap(out var ticket);
177 Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
178 if (authSessionStartState != Steamworks.BeginAuthResult.OK)
180 DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(authSessionStartState));
186 var steamUserId =
new SteamId(steamId);
187 if (remotePeer.Authenticating)
189 remotePeer.UnauthedMessages.Add(
new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer));
193 IWriteMessage outMsg =
new WriteOnlyMessage();
194 WriteSteamId(outMsg, steamUserId);
195 WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
196 outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
198 ForwardToServerProcess(outMsg);
203 public override void Update(
float deltaTime)
205 if (!isActive) {
return; }
207 if (ChildServerRelay.HasShutDown || !ChildServerRelay.IsProcessAlive)
210 Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
211 gameClient?.CreateServerCrashMessage();
215 for (
int i = remotePeers.Count - 1; i >= 0; i--)
217 if (remotePeers[i].DisconnectTime !=
null && remotePeers[i].DisconnectTime < Timing.TotalTime)
219 ClosePeerSession(remotePeers[i]);
223 for (
int i = 0; i < 100; i++)
225 if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) {
break; }
227 var packet = Steamworks.SteamNetworking.ReadP2PPacket();
228 if (packet is {
SteamId: var steamId,
Data: var data })
230 OnP2PData(steamId,
new ReadWriteMessage(data, 0, data.Length * 8,
false));
231 receivedBytes += data.Length;
238 while (ChildServerRelay.Read(out
byte[] incBuf))
240 ChildServerRelay.DisposeLocalHandles();
241 IReadMessage inc =
new ReadOnlyMessage(incBuf,
false, 0, incBuf.Length, ServerConnection);
242 HandleDataMessage(inc);
248 if (!isActive) {
return; }
250 SteamId recipientSteamId = ReadSteamId(inc);
252 var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
254 if (recipientSteamId != selfSteamID)
256 HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc);
260 HandleMessageForOwner(peerPacketHeaders, inc);
264 private static byte[] GetRemainingBytes(IReadMessage msg)
266 return msg.Buffer[msg.BytePosition..msg.LengthBytes];
269 private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, SteamId recipientSteamId, IReadMessage inc)
271 var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
273 if (!packetHeader.IsServerMessage())
275 DebugConsole.ThrowError(
"Received non-server message meant for remote peer");
279 RemotePeer? peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
280 if (peer is
null) {
return; }
282 if (packetHeader.IsDisconnectMessage())
284 var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
285 DisconnectPeer(peer, packet);
289 IWriteMessage outMsg =
new WriteOnlyMessage();
291 outMsg.WriteNetSerializableStruct(
new PeerPacketHeaders
295 Initialization = initialization
298 if (packetHeader.IsConnectionInitializationStep())
300 var initRelayPacket =
new SteamP2PInitializationRelayPacket
302 LobbyID = SteamManager.CurrentLobbyID,
303 Message =
new PeerPacketMessage
305 Buffer = GetRemainingBytes(inc)
309 outMsg.WriteNetSerializableStruct(initRelayPacket);
313 byte[] userMessage = GetRemainingBytes(inc);
314 outMsg.WriteBytes(userMessage, 0, userMessage.Length);
317 ForwardToRemotePeer(deliveryMethod, recipientSteamId, outMsg);
320 private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
322 var (_, packetHeader, _) = peerPacketHeaders;
324 if (packetHeader.IsDisconnectMessage())
326 DebugConsole.ThrowError(
"Received disconnect message from owned server");
330 if (!packetHeader.IsServerMessage())
332 DebugConsole.ThrowError(
"Received non-server message from owned server");
336 if (packetHeader.IsHeartbeatMessage())
341 if (packetHeader.IsConnectionInitializationStep())
343 IWriteMessage outMsg =
new WriteOnlyMessage();
344 WriteSteamId(outMsg, selfSteamID);
345 WriteSteamId(outMsg, selfSteamID);
346 outMsg.WriteNetSerializableStruct(
new PeerPacketHeaders
352 outMsg.WriteNetSerializableStruct(
new SteamP2PInitializationOwnerPacket
354 OwnerName = GameMain.Client.Name
356 ForwardToServerProcess(outMsg);
362 callbacks.OnInitializationComplete.Invoke();
366 PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
367 IReadMessage msg =
new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
368 callbacks.OnMessageReceived.Invoke(msg);
372 private void DisconnectPeer(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
374 peer.DisconnectTime ??= Timing.TotalTime + 1.0;
376 IWriteMessage outMsg =
new WriteOnlyMessage();
377 outMsg.WriteNetSerializableStruct(
new PeerPacketHeaders
382 outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
384 Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
385 sentBytes += outMsg.LengthBytes;
388 private void ClosePeerSession(RemotePeer peer)
390 Steamworks.SteamNetworking.CloseP2PSessionWithUser(peer.SteamId.Value);
391 remotePeers.Remove(peer);
399 public override void Close(PeerDisconnectPacket peerDisconnectPacket)
401 if (!isActive) {
return; }
405 for (
int i = remotePeers.Count - 1; i >= 0; i--)
407 DisconnectPeer(remotePeers[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
412 for (
int i = remotePeers.Count - 1; i >= 0; i--)
414 ClosePeerSession(remotePeers[i]);
417 callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
419 SteamManager.LeaveLobby();
420 Steamworks.SteamNetworking.ResetActions();
421 Steamworks.SteamUser.OnValidateAuthTicketResponse -= OnAuthChange;
426 if (!isActive) {
return; }
429 byte[] msgData = msg.
PrepareForSending(compressPastThreshold, out
bool isCompressed, out _);
430 WriteSteamId(msgToSend, selfSteamID);
431 WriteSteamId(msgToSend, selfSteamID);
432 msgToSend.WriteNetSerializableStruct(
new PeerPacketHeaders
437 msgToSend.WriteNetSerializableStruct(
new PeerPacketMessage
441 ForwardToServerProcess(msgToSend);
444 protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
447 throw new NotImplementedException();
450 private static void ForwardToServerProcess(
IWriteMessage msg)
454 ChildServerRelay.Write(bufToSend);
459 byte[] buf = outMsg.
PrepareForSending(compressPastThreshold:
false, out _, out
int length);
461 if (length + 4 >= MsgConstants.MTU)
463 DebugConsole.Log($
"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
467 bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.
Value, buf, length, 0, deliveryMethod.ToSteam());
470 if (successSend) {
return; }
474 DebugConsole.Log($
"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
475 successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.
Value, buf, length, 0,
DeliveryMethod.Reliable.ToSteam());
481 DebugConsole.AddWarning($
"Failed to send message to remote peer! ({length} bytes)");
486 public override void ForceTimeOut()
readonly NetStats NetStats
void AddValue(NetStatType statType, float value)
override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold=true)
override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
SteamP2POwnerPeer(Callbacks callbacks, int ownerKey)
override void SendPassword(string password)
override void Close(PeerDisconnectPacket peerDisconnectPacket)
override void Update(float deltaTime)
void WriteUInt64(UInt64 val)
byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength)
UnauthedMessage(SteamId sender, byte[] bytes)
static UnspecifiedNone None