Files
RobustToolbox/Robust.Server/ViewVariables/ServerViewVariablesManager.cs
83c2a1be11 [Dependency] source generator part 2 (#6550)
* [Dependency] source generator

No more reflection, no more codegen at runtime

Also various changes to Roslyn helpers to make this easier to write.

Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback.

No fallback is available for [field: Dependency] properties, due to a Roslyn bug.

Code Fixes exist. We love Roslyn

* Apply dependencies generator changes to all code

* Release notes

* Preprocessor got hands

* Handle nullable dependencies

These are bad but gotta deal with it.

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Fine, let's not use collection expressions

---------

Co-authored-by: Moony <moony@hellomouse.net>
2026-05-08 12:38:33 +02:00

326 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.ViewVariables
{
internal sealed partial class ServerViewVariablesManager : ViewVariablesManager, IServerViewVariablesInternal
{
[Dependency] private INetManager _netManager = default!;
[Dependency] private IEntityManager _entityManager = default!;
[Dependency] private IPlayerManager _playerManager = default!;
[Dependency] private IConGroupController _groupController = default!;
[Dependency] private IRobustSerializer _robustSerializer = default!;
[Dependency] private IReflectionManager _reflectionManager = default!;
[Dependency] private IDependencyCollection _dependencyCollection = default!;
[Dependency] private IPrototypeManager _prototypeManager = default!;
private readonly Dictionary<uint, ViewVariablesSession>
_sessions = new();
private readonly Dictionary<NetUserId, List<uint>> _users = new();
private uint _nextSessionId = 1;
public override void Initialize()
{
base.Initialize();
InitializeDomains();
_netManager.RegisterNetMessage<MsgViewVariablesReqSession>(_msgReqSession);
_netManager.RegisterNetMessage<MsgViewVariablesReqData>(_msgReqData);
_netManager.RegisterNetMessage<MsgViewVariablesModifyRemote>(_msgModifyRemote);
_netManager.RegisterNetMessage<MsgViewVariablesCloseSession>(_msgCloseSession);
_netManager.RegisterNetMessage<MsgViewVariablesDenySession>();
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>();
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>();
_playerManager.PlayerStatusChanged += OnStatusChanged;
}
private void OnStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Disconnected)
return;
if (!_users.TryGetValue(e.Session.UserId, out var vvSessions))
return;
foreach (var id in vvSessions)
{
_closeSession(id, false);
}
}
private void _msgCloseSession(MsgViewVariablesCloseSession message)
{
if (!_sessions.TryGetValue(message.SessionId, out var session)
|| session.PlayerUser != message.MsgChannel.UserId)
{
// TODO: logging?
return;
}
_closeSession(message.SessionId, true);
}
private void _msgModifyRemote(MsgViewVariablesModifyRemote message)
{
if (!_sessions.TryGetValue(message.SessionId, out var session)
|| session.PlayerUser != message.MsgChannel.UserId)
{
// TODO: logging?
return;
}
try
{
var value = message.Value;
if (message.ReinterpretValue && !TryReinterpretValue(value, out value))
{
Sawmill.Warning($"Couldn't reinterpret value \"{message.Value}\" sent by {session.PlayerUser}!");
return;
}
session.Modify(message.PropertyIndex, value);
}
catch (ArgumentOutOfRangeException)
{
}
}
private void _msgReqData(MsgViewVariablesReqData message)
{
if (!_sessions.TryGetValue(message.SessionId, out var session)
|| session.PlayerUser != message.MsgChannel.UserId)
{
// TODO: logging?
return;
}
var blob = session.DataRequest(message.RequestMeta);
var dataMsg = new MsgViewVariablesRemoteData();
dataMsg.RequestId = message.RequestId;
dataMsg.Blob = blob;
_netManager.ServerSendMessage(dataMsg, message.MsgChannel);
}
private void _msgReqSession(MsgViewVariablesReqSession message)
{
void Deny(ViewVariablesResponseCode reason)
{
var denyMsg = new MsgViewVariablesDenySession();
denyMsg.RequestId = message.RequestId;
denyMsg.Reason = reason;
_netManager.ServerSendMessage(denyMsg, message.MsgChannel);
}
var player = _playerManager.GetSessionByChannel(message.MsgChannel);
if (!_groupController.CanCommand(player, "vv"))
{
Deny(ViewVariablesResponseCode.NoAccess);
return;
}
object theObject;
Action<object>? objectChangeDelegate = null;
switch (message.Selector)
{
case ViewVariablesComponentSelector componentSelector:
{
var compType = _reflectionManager.GetType(componentSelector.ComponentType);
var entity = _entityManager.GetEntity(componentSelector.Entity);
if (compType == null ||
!_entityManager.TryGetComponent(entity, compType, out var component))
{
Deny(ViewVariablesResponseCode.NoObject);
return;
}
theObject = component;
break;
}
case ViewVariablesEntitySelector entitySelector:
{
var entity = _entityManager.GetEntity(entitySelector.Entity);
if (!_entityManager.EntityExists(entity))
{
Deny(ViewVariablesResponseCode.NoObject);
return;
}
theObject = entitySelector.Entity;
break;
}
case ViewVariablesSessionRelativeSelector sessionRelativeSelector:
{
if (!_sessions.TryGetValue(sessionRelativeSelector.SessionId, out var relSession)
|| relSession.PlayerUser != message.MsgChannel.UserId)
{
// TODO: logging?
Deny(ViewVariablesResponseCode.NoObject);
return;
}
object? value;
try
{
if (!relSession.TryGetRelativeObject(sessionRelativeSelector.PropertyIndex, out value))
{
Deny(ViewVariablesResponseCode.InvalidRequest);
return;
}
}
catch (ArgumentOutOfRangeException)
{
Deny(ViewVariablesResponseCode.NoObject);
return;
}
catch (Exception e)
{
Sawmill.Error("Exception while retrieving value for session. {0}", e);
Deny(ViewVariablesResponseCode.NoObject);
return;
}
if (value == null)
{
Deny(ViewVariablesResponseCode.NoObject);
return;
}
theObject = value;
objectChangeDelegate = obj => relSession.Modify(sessionRelativeSelector.PropertyIndex, obj);
break;
}
case ViewVariablesIoCSelector ioCSelector:
{
var reflectionManager = _reflectionManager;
if (!reflectionManager.TryLooseGetType(ioCSelector.TypeName, out var type))
{
Deny(ViewVariablesResponseCode.InvalidRequest);
return;
}
theObject = _dependencyCollection.ResolveType(type);
break;
}
case ViewVariablesEntitySystemSelector esSelector:
{
var reflectionManager = _reflectionManager;
if (!reflectionManager.TryLooseGetType(esSelector.TypeName, out var type))
{
Deny(ViewVariablesResponseCode.InvalidRequest);
return;
}
theObject = _entityManager.EntitySysManager.GetEntitySystem(type);
break;
}
case ViewVariablesPathSelector paSelector:
{
if (ResolvePath(paSelector.Path)?.Get() is not {} obj)
{
Deny(ViewVariablesResponseCode.NoObject);
return;
}
theObject = obj;
break;
}
default:
Deny(ViewVariablesResponseCode.InvalidRequest);
return;
}
var sessionId = _nextSessionId++;
var session = new ViewVariablesSession(message.MsgChannel.UserId, theObject, objectChangeDelegate, sessionId, this,
_robustSerializer, _entityManager, Sawmill);
_sessions.Add(sessionId, session);
_users.GetOrNew(session.PlayerUser).Add(sessionId);
var allowMsg = new MsgViewVariablesOpenSession();
allowMsg.RequestId = message.RequestId;
allowMsg.SessionId = session.SessionId;
_netManager.ServerSendMessage(allowMsg, message.MsgChannel);
}
private void _closeSession(uint sessionId, bool sendMsg)
{
if (!_sessions.TryGetValue(sessionId, out var session))
{
return;
}
_sessions.Remove(sessionId);
if (!sendMsg || !_playerManager.TryGetSessionById(session.PlayerUser, out var player) ||
player.Status == SessionStatus.Disconnected)
{
return;
}
var closeMsg = new MsgViewVariablesCloseSession();
closeMsg.SessionId = session.SessionId;
_netManager.ServerSendMessage(closeMsg, player.Channel);
}
private bool TryReinterpretValue(object? input, [NotNullWhen(true)] out object? output)
{
output = null;
switch (input)
{
case ViewVariablesBlobMembers.PrototypeReferenceToken token:
if (!_prototypeManager.TryGetKindType(token.Variant, out var variantType))
return false;
if (!_prototypeManager.TryIndex(variantType, token.ID, out var prototype))
return false;
output = prototype;
return true;
default:
return false;
}
}
protected override bool CheckPermissions(INetChannel channel, string command)
{
return _playerManager.TryGetSessionByChannel(channel, out var session) && _groupController.CanCommand(session, command);
}
protected override bool TryGetSession(Guid guid, [NotNullWhen(true)] out ICommonSession? session)
{
if (guid != Guid.Empty
&& _playerManager.TryGetSessionById(new NetUserId(guid), out var player)
&& !_groupController.CanCommand(player, "vv")) // Can't VV other admins.
{
session = player;
return true;
}
session = null;
return false;
}
}
}