mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-06 21:38:32 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
464 lines
12 KiB
C#
464 lines
12 KiB
C#
using Sandbox.Engine;
|
|
using Sandbox.Internal;
|
|
|
|
namespace Sandbox.Network;
|
|
|
|
/// <summary>
|
|
/// A network system is a bunch of connections that people can send messages
|
|
/// over. Right now it can be a dedicated server, a listen server, a pure client,
|
|
/// or a p2p system.
|
|
/// </summary>
|
|
internal partial class NetworkSystem
|
|
{
|
|
void InstallHandshakeMessages()
|
|
{
|
|
AddHandler<ServerInfo>( On_Handshake_ServerInfo );
|
|
AddHandler<UserInfo>( On_Handshake_ClientInfo );
|
|
AddHandler<Welcome>( On_Handshake_Welcome );
|
|
AddHandler<RequestInitialSnapshot>( On_Handshake_RequestSnapshot );
|
|
AddHandler<InitialSnapshotResponse>( On_Handshake_Snapshot );
|
|
AddHandler<MountedVPKsResponse>( On_Handshake_MountedVPKs );
|
|
AddHandler<RequestMountedVPKs>( On_Handshake_RequestMountedVPKs );
|
|
AddHandler<ClientReady>( On_Handshake_ClientReady );
|
|
AddHandler<Activate>( On_Handshake_Activate );
|
|
AddHandler<RestartHandshakeMsg>( On_Handshake_Restart );
|
|
AddHandler<KickMsg>( On_Kick );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server says hello to the client. It tells the client some basic information about itself.
|
|
/// The client can determine here whether they still want to join or not.
|
|
/// </summary>
|
|
async Task On_Handshake_ServerInfo( ServerInfo msg, Connection source, Guid msgId )
|
|
{
|
|
if ( !source.IsHost )
|
|
return;
|
|
|
|
IsDeveloperHost = msg.IsDeveloperHost;
|
|
|
|
source.UpdateFrom( msg.Host );
|
|
source.State = Connection.ChannelState.LoadingServerInformation;
|
|
|
|
Connection.Local = new LocalConnection( msg.Assigned.Id )
|
|
{
|
|
HandshakeId = msg.HandshakeId,
|
|
State = Connection.ChannelState.LoadingServerInformation
|
|
};
|
|
|
|
//
|
|
// Tell the server all about ourselves
|
|
//
|
|
var output = UserInfo.Local;
|
|
|
|
if ( !source.OnReceiveServerInfo( ref output, msg ) )
|
|
{
|
|
IGameInstanceDll.Current.Disconnect();
|
|
return;
|
|
}
|
|
|
|
log.Trace( $"Server Id is {source.Id}" );
|
|
log.Trace( $"Map Name is {msg.MapName}" );
|
|
log.Trace( $"Server Name is {msg.ServerName}" );
|
|
log.Trace( $"Engine version is {msg.EngineVersion}" );
|
|
log.Trace( $"Game Package is {msg.GamePackage}" );
|
|
log.Trace( $"My Client ID is {Connection.Local.Id}" );
|
|
|
|
// This is a bit of a mess, it needs a good cleaning up. If they have a menu package, then load it first.
|
|
if ( !string.IsNullOrEmpty( msg.GamePackage ) )
|
|
{
|
|
UpdateLoading( $"Loading {msg.GamePackage}" );
|
|
|
|
log.Trace( $"Loading menu package.. {msg.GamePackage}" );
|
|
|
|
var flags = GameLoadingFlags.Remote | GameLoadingFlags.Reload;
|
|
if ( IsDeveloperHost ) flags |= GameLoadingFlags.Developer;
|
|
|
|
if ( !Application.IsStandalone )
|
|
{
|
|
LaunchArguments.Map = msg.MapPackage;
|
|
await IGameInstanceDll.Current.LoadGamePackageAsync( msg.GamePackage, flags, default );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.Trace( $"No game package - must be a developer" );
|
|
}
|
|
|
|
if ( IGameInstanceDll.Current is not null )
|
|
{
|
|
// TypeLibrary was probably rebuilt, keep it up to date
|
|
TypeLibrary = IGameInstanceDll.Current.TypeLibrary;
|
|
}
|
|
|
|
foreach ( var (k, v) in msg.ServerData )
|
|
{
|
|
Networking.SetData( k, v );
|
|
}
|
|
|
|
Networking.MaxPlayers = msg.MaxPlayers;
|
|
Networking.ServerName = msg.ServerName;
|
|
Networking.MapName = msg.MapName;
|
|
|
|
InstallStringTables();
|
|
log.Trace( $"Fetching Server Data.." );
|
|
|
|
//
|
|
// Tell me what I need
|
|
//
|
|
UpdateLoading( $"Fetching Server Data" );
|
|
|
|
source.SendMessage( output with
|
|
{
|
|
HandshakeId = msg.HandshakeId
|
|
} );
|
|
}
|
|
|
|
Task On_Handshake_ClientInfo( UserInfo msg, Connection source, Guid msgId )
|
|
{
|
|
if ( source.IsHost )
|
|
return Task.CompletedTask;
|
|
|
|
if ( msg.HandshakeId != source.HandshakeId )
|
|
return Task.CompletedTask;
|
|
|
|
if ( source.State != Connection.ChannelState.LoadingServerInformation )
|
|
{
|
|
source.Kick( $"Invalid Handshake State {source.State}" );
|
|
Log.Info( $"Kicking {source.DisplayName} [{source.SteamId}] Invalid Handshake State {source.State}" );
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
if ( !source.OnReceiveUserInfo( msg ) )
|
|
return Task.CompletedTask;
|
|
|
|
//
|
|
// Lobbies and steam network connections are trusted so we can take the display name and Steam Id from them
|
|
// we shouldn't trust any other type of connection... but local TCP we can let slide.
|
|
//
|
|
if ( source is SteamLobbyConnection slob )
|
|
{
|
|
var friend = new Friend( slob.Friend.Id );
|
|
msg.SteamId = slob.Friend.Id;
|
|
msg.DisplayName = friend.Name;
|
|
}
|
|
|
|
Log.Info( $"{msg.DisplayName} [{msg.SteamId}] is connecting" );
|
|
|
|
var denialReason = "";
|
|
|
|
source.PreInfo = new ConnectionInfo( null )
|
|
{
|
|
ConnectionId = source.Id,
|
|
State = source.State
|
|
};
|
|
|
|
source.PreInfo.Update( msg );
|
|
|
|
if ( GameSystem is not null && !GameSystem.AcceptConnection( source, ref denialReason ) )
|
|
{
|
|
Log.Info( $"Kicking {msg.DisplayName} [{msg.SteamId}] - {denialReason}" );
|
|
source.Kick( denialReason );
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
source.PreInfo = null;
|
|
source.State = Connection.ChannelState.Welcome;
|
|
|
|
//log.Info( $"Client Name is {data.DisplayName}" );
|
|
//log.Info( $"Client SteamId is {data.SteamId}" );
|
|
//log.Info( $"Client EngineVersion is {data.EngineVersion}" );
|
|
|
|
var output = new Welcome();
|
|
output.HandshakeId = msg.HandshakeId;
|
|
|
|
foreach ( var table in tables )
|
|
{
|
|
table.SendSnapshot( source );
|
|
}
|
|
|
|
//
|
|
// They're connected now dummy
|
|
//
|
|
msg.ConnectionTime = DateTime.UtcNow;
|
|
|
|
//
|
|
// Make their name unique
|
|
//
|
|
var displayName = msg.DisplayName;
|
|
var index = 2;
|
|
while ( ConnectionInfo.All.Values.Any( x => string.Equals( x.DisplayName, displayName, StringComparison.OrdinalIgnoreCase ) ) )
|
|
{
|
|
displayName = $"{msg.DisplayName} ({index})";
|
|
index++;
|
|
}
|
|
msg.DisplayName = displayName;
|
|
|
|
//
|
|
// Add player info to the manager. This will get sent to all the other players
|
|
// so this player is part of the game now.
|
|
//
|
|
{
|
|
AddConnection( source, msg );
|
|
}
|
|
|
|
//
|
|
// Tell game this guy has connected. This happens after ConnectionInfo so that
|
|
// everyone can look up their name etc.
|
|
//
|
|
GameSystem?.OnConnected( source );
|
|
|
|
source.SendMessage( output );
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
async Task On_Handshake_Welcome( Welcome msg, Connection source, Guid msgId )
|
|
{
|
|
if ( !source.IsHost )
|
|
return;
|
|
|
|
if ( Connection.Local is null )
|
|
throw new UnauthorizedAccessException();
|
|
|
|
if ( msg.HandshakeId != Connection.Local.HandshakeId )
|
|
return;
|
|
|
|
if ( Connection.Local.State != Connection.ChannelState.LoadingServerInformation )
|
|
throw new UnauthorizedAccessException();
|
|
|
|
Connection.Local.State = Connection.ChannelState.Welcome;
|
|
|
|
log.Trace( $"Welcome!" );
|
|
|
|
UpdateLoading( $"Loading Network Tables" );
|
|
|
|
await IGameInstanceDll.Current?.LoadNetworkTables( this );
|
|
|
|
UpdateLoading( $"Init Game System" );
|
|
|
|
InitializeGameSystem();
|
|
|
|
log.Trace( $"Game Network System: {GameSystem}" );
|
|
|
|
//
|
|
// Here would be a goodish place to send a bunch of CRC's of the loaded state, so
|
|
// the server can compare and reject if we're loading assemblies wrong (cheater)
|
|
//
|
|
UpdateLoading( "Fetching Snapshot" );
|
|
|
|
var output = new RequestMountedVPKs { HandshakeId = msg.HandshakeId };
|
|
source.SendMessage( output );
|
|
}
|
|
|
|
Task On_Handshake_RequestMountedVPKs( RequestMountedVPKs msg, Connection source, Guid msgId )
|
|
{
|
|
if ( source.IsHost )
|
|
return Task.CompletedTask;
|
|
|
|
if ( msg.HandshakeId != source.HandshakeId )
|
|
return Task.CompletedTask;
|
|
|
|
if ( source.State != Connection.ChannelState.Welcome )
|
|
{
|
|
source.Kick( $"Invalid Handshake State {source.State}" );
|
|
Log.Info( $"Kicking {source.DisplayName} [{source.SteamId}] Invalid Handshake State {source.State}" );
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
var output = new MountedVPKsResponse { HandshakeId = msg.HandshakeId };
|
|
|
|
GameSystem?.GetMountedVPKs( source, ref output );
|
|
|
|
source.State = Connection.ChannelState.MountVPKs;
|
|
source.SendMessage( output );
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
async Task On_Handshake_MountedVPKs( MountedVPKsResponse msg, Connection source, Guid msgId )
|
|
{
|
|
if ( !source.IsHost )
|
|
return;
|
|
|
|
if ( Connection.Local is null )
|
|
throw new UnauthorizedAccessException();
|
|
|
|
if ( msg.HandshakeId != Connection.Local.HandshakeId )
|
|
return;
|
|
|
|
if ( Connection.Local.State != Connection.ChannelState.Welcome )
|
|
throw new UnauthorizedAccessException();
|
|
|
|
Connection.Local.State = Connection.ChannelState.MountVPKs;
|
|
|
|
await GameSystem?.MountVPKs( source, msg );
|
|
|
|
var userInfo = UserInfo.Local;
|
|
var output = new RequestInitialSnapshot();
|
|
|
|
output.HandshakeId = msg.HandshakeId;
|
|
output.UserData = userInfo.UserData;
|
|
|
|
source.SendMessage( output );
|
|
}
|
|
|
|
Task On_Handshake_RequestSnapshot( RequestInitialSnapshot msg, Connection source, Guid msgId )
|
|
{
|
|
if ( source.IsHost )
|
|
return Task.CompletedTask;
|
|
|
|
if ( msg.HandshakeId != source.HandshakeId )
|
|
return Task.CompletedTask;
|
|
|
|
if ( source.State != Connection.ChannelState.MountVPKs )
|
|
{
|
|
source.Kick( $"Invalid Handshake State {source.State}" );
|
|
Log.Info( $"Kicking {source.DisplayName} [{source.SteamId}] Invalid Handshake State {source.State}" );
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
if ( msg.UserData is not null )
|
|
{
|
|
source.UpdateUserData( msg.UserData );
|
|
}
|
|
|
|
source.State = Connection.ChannelState.Snapshot;
|
|
|
|
log.Trace( $"[{this}] Requesting a snapshot" );
|
|
|
|
var snapshot = new SnapshotMsg
|
|
{
|
|
GameObjectSystems = [],
|
|
NetworkObjects = new( 64 )
|
|
};
|
|
|
|
GameSystem?.GetSnapshot( source, ref snapshot );
|
|
|
|
var output = new InitialSnapshotResponse
|
|
{
|
|
HandshakeId = source.HandshakeId,
|
|
Snapshot = snapshot
|
|
};
|
|
|
|
source.SendMessage( output );
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
async Task On_Handshake_Snapshot( InitialSnapshotResponse msg, Connection source, Guid msgId )
|
|
{
|
|
NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Snapshot, msg );
|
|
|
|
if ( !source.IsHost )
|
|
return;
|
|
|
|
if ( Connection.Local is null )
|
|
throw new UnauthorizedAccessException();
|
|
|
|
if ( msg.HandshakeId != Connection.Local.HandshakeId )
|
|
return;
|
|
|
|
if ( Connection.Local.State != Connection.ChannelState.MountVPKs )
|
|
throw new UnauthorizedAccessException();
|
|
|
|
Connection.Local.State = Connection.ChannelState.Snapshot;
|
|
|
|
UpdateLoading( "Loading Snapshot" );
|
|
Log.Trace( $"[{this}] Got a snapshot" );
|
|
|
|
//
|
|
// Spawn the scene, which could also lead to calling OnStart, OnEnable on components
|
|
// which might create new network instances. So from this point we should be considered
|
|
// live in the game.
|
|
//
|
|
if ( GameSystem is not null )
|
|
{
|
|
await GameSystem.SetSnapshotAsync( msg.Snapshot );
|
|
}
|
|
|
|
Log.Trace( $"[{this}] Finished loading snapshot" );
|
|
|
|
var output = new ClientReady();
|
|
output.HandshakeId = msg.HandshakeId;
|
|
|
|
source.SendMessage( output );
|
|
}
|
|
|
|
Task On_Handshake_ClientReady( ClientReady msg, Connection source, Guid msgId )
|
|
{
|
|
if ( source.IsHost )
|
|
return Task.CompletedTask;
|
|
|
|
if ( msg.HandshakeId != source.HandshakeId )
|
|
return Task.CompletedTask;
|
|
|
|
Log.Trace( $"[{this}] Client is ready" );
|
|
|
|
if ( source.State != Connection.ChannelState.Snapshot )
|
|
{
|
|
source.Kick( $"Invalid Handshake State {source.State}" );
|
|
Log.Info( $"Kicking {source.DisplayName} [{source.SteamId}] Invalid Handshake State {source.State}" );
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
source.State = Connection.ChannelState.Connected;
|
|
|
|
//
|
|
// Tell game this guy is fully active.
|
|
//
|
|
GameSystem?.OnJoined( source );
|
|
|
|
var output = new Activate();
|
|
output.HandshakeId = msg.HandshakeId;
|
|
|
|
source.SendMessage( output );
|
|
|
|
Log.Info( $"{source.DisplayName} [{source.SteamId}] is connected" );
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
Task On_Handshake_Restart( RestartHandshakeMsg msg, Connection source, Guid msgId )
|
|
{
|
|
if ( source.IsHost )
|
|
return Task.CompletedTask;
|
|
|
|
StartHandshake( source );
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
Task On_Kick( KickMsg msg, Connection source, Guid msgId )
|
|
{
|
|
if ( !source.IsHost )
|
|
{
|
|
// Conna: only the host can kick us.
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
IGameInstanceDll.Current.Disconnect();
|
|
IMenuSystem.ShowServerError( "Disconnected", msg.Reason );
|
|
Log.Warning( $"Disconnecting - {msg.Reason}" );
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
Task On_Handshake_Activate( Activate msg, Connection source, Guid msgId )
|
|
{
|
|
if ( !source.IsHost )
|
|
return Task.CompletedTask;
|
|
|
|
if ( Connection.Local is null )
|
|
throw new UnauthorizedAccessException();
|
|
|
|
if ( msg.HandshakeId != Connection.Local.HandshakeId )
|
|
return Task.CompletedTask;
|
|
|
|
Log.Trace( $"[{this}] I am spawning into the game!" );
|
|
LoadingScreen.IsVisible = false;
|
|
|
|
Connection.Local.State = Connection.ChannelState.Connected;
|
|
source.State = Connection.ChannelState.Connected;
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|