mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-02 11:28:19 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
400 lines
12 KiB
C#
400 lines
12 KiB
C#
using Sandbox.Utility;
|
|
using System.ComponentModel;
|
|
using System.Reflection;
|
|
|
|
namespace Sandbox;
|
|
|
|
public static partial class Rpc
|
|
{
|
|
/// <summary>
|
|
/// An instance RPC call is incoming from the network. Look up the method and call it.
|
|
/// </summary>
|
|
internal static void IncomingInstanceRpcMsg( ObjectRpcMsg rpc, Connection source )
|
|
{
|
|
NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Rpc, rpc );
|
|
|
|
if ( rpc.Guid == Guid.Empty )
|
|
{
|
|
Log.Warning( $"OnObjectMessage: Failed to call RPC with identity '{rpc.MethodIdentity}' for unknown GameObject" );
|
|
return;
|
|
}
|
|
|
|
if ( Game.ActiveScene is null )
|
|
{
|
|
var targetMethod = Game.TypeLibrary.GetMemberByIdent( rpc.MethodIdentity )?.MemberInfo as MethodInfo;
|
|
Log.Warning( $"OnObjectMessage: Invalid Scene for RPC {targetMethod?.Name ?? "Unknown"}" );
|
|
|
|
return;
|
|
}
|
|
|
|
var gameObject = Game.ActiveScene.Directory.FindByGuid( rpc.Guid );
|
|
if ( gameObject is null )
|
|
{
|
|
var targetMethod = Game.TypeLibrary.GetMemberByIdent( rpc.MethodIdentity )?.MemberInfo as MethodInfo;
|
|
Log.Warning( $"OnObjectMessage: Unknown GameObject {rpc.Guid} for RPC {targetMethod?.Name ?? "Unknown"}" );
|
|
|
|
return;
|
|
}
|
|
|
|
// If we don't have a component, then we're calling a method on the GameObject itself
|
|
if ( rpc.ComponentId == Guid.Empty )
|
|
{
|
|
InvokeInstanceRpc( rpc, Game.TypeLibrary.GetType( typeof( GameObject ) ), gameObject, source );
|
|
return;
|
|
}
|
|
|
|
// Find target method on component
|
|
var component = Game.ActiveScene.Directory.FindComponentByGuid( rpc.ComponentId );
|
|
if ( component is null )
|
|
{
|
|
var targetMethod = Game.TypeLibrary.GetMemberByIdent( rpc.MethodIdentity )?.MemberInfo as MethodInfo;
|
|
Log.Warning( $"OnObjectMessage: Unknown Component {rpc.ComponentId} on {gameObject.Name} for RPC {targetMethod?.Name ?? "Unknown"}" );
|
|
return;
|
|
}
|
|
|
|
// Invoke on component
|
|
var typeDesc = Game.TypeLibrary.GetType( component.GetType() );
|
|
InvokeInstanceRpc( rpc, typeDesc, component, source );
|
|
}
|
|
|
|
/// <summary>
|
|
/// An instance RPC call is incoming from the network. Look up the method and call it.
|
|
/// </summary>
|
|
internal static void IncomingInstanceRpcMsg( SceneRpcMsg message, Connection source )
|
|
{
|
|
NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Rpc, message );
|
|
|
|
if ( message.Guid == Guid.Empty )
|
|
{
|
|
Log.Warning( $"OnObjectMessage: Failed to call RPC with identity '{message.MethodIdentity}' for unknown GameObject" );
|
|
return;
|
|
}
|
|
|
|
if ( Game.ActiveScene is null )
|
|
{
|
|
var targetMethod = Game.TypeLibrary.GetMemberByIdent( message.MethodIdentity )?.MemberInfo as MethodInfo;
|
|
Log.Warning( $"OnObjectMessage: Invalid Scene for RPC {targetMethod?.Name ?? "Unknown"}" );
|
|
|
|
return;
|
|
}
|
|
|
|
var system = Game.ActiveScene.Directory.FindSystemByGuid( message.Guid );
|
|
|
|
if ( system is null )
|
|
{
|
|
var targetMethod = Game.TypeLibrary.GetMemberByIdent( message.MethodIdentity )?.MemberInfo as MethodInfo;
|
|
Log.Warning( $"OnObjectMessage: Invalid GameObjectSystem for RPC {targetMethod?.Name ?? "Unknown"}" );
|
|
|
|
return;
|
|
}
|
|
|
|
// Invoke on GameObjectSystem
|
|
InvokeInstanceRpc( message, system, source );
|
|
}
|
|
|
|
static Superluminal _ph = new Superluminal( "Rpc", Color.Cyan );
|
|
|
|
static void InvokeInstanceRpc( in SceneRpcMsg rpc, in object targetObject, in Connection source )
|
|
{
|
|
var typeDesc = Game.TypeLibrary.GetType( targetObject.GetType() );
|
|
var method = Game.TypeLibrary.GetMemberByIdent( rpc.MethodIdentity )?.MemberInfo as MethodInfo;
|
|
|
|
if ( method is { DeclaringType.IsGenericTypeDefinition: true } )
|
|
{
|
|
// If called method was declared on a generic type, we need to find the right
|
|
// generic instance type.
|
|
method = typeDesc.TargetType.GetInheritedConstructedGenericType( method.DeclaringType )?.GetMemberWithSameMetadataDefinitionAs( method ) as MethodInfo;
|
|
}
|
|
|
|
if ( method is null )
|
|
{
|
|
Log.Error( $"Unknown RPC with identity '{rpc.MethodIdentity}' on {typeDesc.Name}" );
|
|
return;
|
|
}
|
|
|
|
if ( !method.HasAttribute( typeof( RpcAttribute ) ) )
|
|
{
|
|
source.Kick( "Unauthorized RPC" );
|
|
return;
|
|
}
|
|
|
|
var rpcName = $"{typeDesc.FullName}.{method.Name}";
|
|
using var profiler = _ph.Start( rpcName );
|
|
|
|
NetworkDebugSystem.Current?.Track( rpcName, rpc );
|
|
|
|
using ( WithCaller( source ) )
|
|
{
|
|
try
|
|
{
|
|
method.Invoke( targetObject, rpc.Arguments );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Log.Error( e, $"Error calling RPC '{typeDesc.Name}.{method.Name}' - {e.Message}" );
|
|
}
|
|
}
|
|
}
|
|
|
|
static void InvokeInstanceRpc( in ObjectRpcMsg rpc, in TypeDescription typeDesc, in object targetObject, in Connection source )
|
|
{
|
|
var method = Game.TypeLibrary.GetMemberByIdent( rpc.MethodIdentity )?.MemberInfo as MethodInfo;
|
|
|
|
if ( method is { DeclaringType.IsGenericTypeDefinition: true } )
|
|
{
|
|
// If called method was declared on a generic type, we need to find the right
|
|
// generic instance type.
|
|
method = typeDesc.TargetType.GetInheritedConstructedGenericType( method.DeclaringType )?.GetMemberWithSameMetadataDefinitionAs( method ) as MethodInfo;
|
|
}
|
|
|
|
if ( method is null )
|
|
{
|
|
Log.Error( $"Unknown RPC with identity '{rpc.MethodIdentity}' on {typeDesc.Name}" );
|
|
return;
|
|
}
|
|
|
|
if ( !method.HasAttribute( typeof( RpcAttribute ) ) )
|
|
{
|
|
source.Kick( "Unauthorized RPC" );
|
|
return;
|
|
}
|
|
|
|
var rpcName = $"{typeDesc.FullName}.{method.Name}";
|
|
using var profiler = _ph.Start( rpcName );
|
|
|
|
NetworkDebugSystem.Current?.Track( rpcName, rpc );
|
|
|
|
using ( WithCaller( source ) )
|
|
{
|
|
try
|
|
{
|
|
method.Invoke( targetObject, rpc.Arguments );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Log.Error( e, $"Error calling RPC '{typeDesc.Name}.{method.Name}' - {e.Message}" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does the current caller have permission to invoke the RPC?
|
|
/// </summary>
|
|
static bool HasHostInstancePermission( Connection caller, NetFlags permission )
|
|
{
|
|
if ( permission.Contains( NetFlags.HostOnly ) && !caller.IsHost )
|
|
return false;
|
|
|
|
return !permission.Contains( NetFlags.OwnerOnly ) || caller.IsHost;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does the current caller have permission to invoke the RPC?
|
|
/// </summary>
|
|
static bool HasInstancePermission( Connection caller, GameObject go, NetFlags permission )
|
|
{
|
|
if ( permission.Contains( NetFlags.HostOnly ) && !caller.IsHost )
|
|
return false;
|
|
|
|
if ( permission.Contains( NetFlags.OwnerOnly ) )
|
|
{
|
|
if ( !go.IsValid() )
|
|
{
|
|
// Conna: if the RPC attribute permission is OwnerOnly but the game object is invalid,
|
|
// then we can't be sure that we are the owner or have permission. So assume we don't.
|
|
return false;
|
|
}
|
|
|
|
var hasOwner = go.Network.OwnerId != Guid.Empty;
|
|
|
|
if ( (hasOwner && go.Network.OwnerId != caller.Id) || (!hasOwner && !caller.IsHost) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when an instance RPC is called for a <see cref="Scene"/> and <see cref="GameObjectSystem"/>.
|
|
/// </summary>
|
|
[EditorBrowsable( EditorBrowsableState.Never )]
|
|
public static void OnCallInstanceRpc( in GameObjectSystem system, in WrappedMethod m, in object[] argumentList )
|
|
{
|
|
var attribute = m.GetAttribute<RpcAttribute>();
|
|
if ( attribute is null ) return;
|
|
|
|
//
|
|
// Send over network
|
|
//
|
|
if ( !Calling && Networking.IsActive )
|
|
{
|
|
SendInstanceRpc( system, m, argumentList, attribute );
|
|
}
|
|
|
|
// Was filtered out
|
|
if ( Filter.HasValue && !Filter.Value.IsRecipient( Connection.Local ) ) return;
|
|
|
|
// We're the owner if we're the host
|
|
var isOwner = Networking.IsHost;
|
|
|
|
// Was not included in the filter
|
|
if ( attribute.Mode == RpcMode.Owner && !isOwner ) return;
|
|
if ( attribute.Mode == RpcMode.Host && !Networking.IsHost ) return;
|
|
|
|
PreCall();
|
|
|
|
if ( !HasHostInstancePermission( Caller ?? Connection.Local, attribute.Flags ) ) return;
|
|
|
|
Resume( m );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when an instance RPC is called for a <see cref="GameObject"/> and <see cref="Component"/>.
|
|
/// </summary>
|
|
[EditorBrowsable( EditorBrowsableState.Never )]
|
|
public static void OnCallInstanceRpc( in GameObject go, in Component component, in WrappedMethod m, in object[] argumentList )
|
|
{
|
|
var attribute = m.GetAttribute<RpcAttribute>();
|
|
if ( attribute is null ) return;
|
|
|
|
//
|
|
// Send over network
|
|
//
|
|
if ( !Calling && Networking.IsActive )
|
|
{
|
|
SendInstanceRpc( go, component, m, argumentList, attribute );
|
|
}
|
|
|
|
// Was filtered out
|
|
if ( Filter.HasValue && !Filter.Value.IsRecipient( Connection.Local ) ) return;
|
|
|
|
// We're the owner if we are the owner or if it has no owner and we're the host
|
|
var isOwner = go.Network.IsOwner || (go.Network.OwnerId == Guid.Empty && Networking.IsHost);
|
|
|
|
// Was not included in the filter
|
|
if ( attribute.Mode == RpcMode.Owner && !isOwner ) return;
|
|
if ( attribute.Mode == RpcMode.Host && !Networking.IsHost ) return;
|
|
|
|
PreCall();
|
|
|
|
// Can they even call this shit
|
|
if ( !HasInstancePermission( Caller ?? Connection.Local, go, attribute.Flags ) ) return;
|
|
|
|
Resume( m );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Do the actual send of the instance RPC.
|
|
/// </summary>
|
|
private static void SendInstanceRpc( GameObjectSystem system, in WrappedMethod m, object[] argumentList, RpcAttribute attribute )
|
|
{
|
|
if ( !system.Scene.IsValid() ) return;
|
|
|
|
var networkSystem = SceneNetworkSystem.Instance;
|
|
if ( networkSystem is null )
|
|
{
|
|
Log.Warning( "SceneNetworkSystem.Instance is null when sending RPC" );
|
|
return;
|
|
}
|
|
|
|
var msg = new SceneRpcMsg
|
|
{
|
|
Guid = system.Id,
|
|
MethodIdentity = m.MethodIdentity,
|
|
Arguments = argumentList
|
|
};
|
|
|
|
//
|
|
// To everyone
|
|
//
|
|
if ( attribute.Mode == RpcMode.Broadcast )
|
|
{
|
|
networkSystem.Broadcast( msg, Filter, attribute.Flags );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// To the owner of the object
|
|
//
|
|
if ( attribute.Mode == RpcMode.Owner )
|
|
{
|
|
var targetId = Connection.Host?.Id ?? Guid.Empty;
|
|
if ( targetId == Connection.Local.Id ) return; // don't send to ourselves
|
|
if ( targetId == Guid.Empty ) return; // don't send to no-one
|
|
|
|
networkSystem.Send( targetId, msg, attribute.Flags );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// To the host of the server
|
|
//
|
|
if ( attribute.Mode == RpcMode.Host )
|
|
{
|
|
var targetId = Connection.Host?.Id ?? Guid.Empty;
|
|
if ( targetId == Connection.Local.Id ) return; // don't send to ourselves
|
|
if ( targetId == Guid.Empty ) return; // don't send to no-one
|
|
|
|
networkSystem.Send( targetId, msg, attribute.Flags );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Do the actual send of the instance RPC.
|
|
/// </summary>
|
|
private static void SendInstanceRpc( GameObject go, Component component, in WrappedMethod m, object[] argumentList, RpcAttribute attribute )
|
|
{
|
|
if ( !go.IsValid() ) return;
|
|
|
|
var networkSystem = SceneNetworkSystem.Instance;
|
|
if ( networkSystem is null )
|
|
{
|
|
Log.Warning( "SceneNetworkSystem.Instance is null when sending RPC" );
|
|
return;
|
|
}
|
|
|
|
var msg = new ObjectRpcMsg
|
|
{
|
|
Guid = go.Id,
|
|
ComponentId = component?.Id ?? Guid.Empty,
|
|
MethodIdentity = m.MethodIdentity,
|
|
Arguments = argumentList
|
|
};
|
|
|
|
//
|
|
// To everyone
|
|
//
|
|
if ( attribute.Mode == RpcMode.Broadcast )
|
|
{
|
|
networkSystem.Broadcast( msg, Filter, attribute.Flags );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// To the owner of the object
|
|
//
|
|
if ( attribute.Mode == RpcMode.Owner )
|
|
{
|
|
var targetId = go.Network.OwnerId;
|
|
if ( targetId == Guid.Empty ) targetId = Connection.Host?.Id ?? Guid.Empty; // host is the owner, fallback
|
|
if ( targetId == Connection.Local.Id ) return; // don't send to ourselves
|
|
if ( targetId == Guid.Empty ) return; // don't send to no-one
|
|
|
|
networkSystem.Send( targetId, msg, attribute.Flags );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// To the host of the server
|
|
//
|
|
if ( attribute.Mode == RpcMode.Host )
|
|
{
|
|
var targetId = Connection.Host?.Id ?? Guid.Empty;
|
|
if ( targetId == Connection.Local.Id ) return; // don't send to ourselves
|
|
if ( targetId == Guid.Empty ) return; // don't send to no-one
|
|
|
|
networkSystem.Send( targetId, msg, attribute.Flags );
|
|
}
|
|
}
|
|
}
|