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.Log; using Robust.Shared.Network; using Robust.Shared.Network.Messages; using Robust.Shared.Players; using Robust.Shared.Prototypes; using Robust.Shared.Reflection; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Robust.Server.ViewVariables { internal sealed partial class ServerViewVariablesManager : ViewVariablesManager, IServerViewVariablesInternal { [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IConGroupController _groupController = default!; [Dependency] private readonly IRobustSerializer _robustSerializer = default!; [Dependency] private readonly IReflectionManager _reflectionManager = default!; [Dependency] private readonly IDependencyCollection _dependencyCollection = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private readonly Dictionary _sessions = new(); private uint _nextSessionId = 1; public override void Initialize() { base.Initialize(); InitializeDomains(); _netManager.RegisterNetMessage(_msgReqSession); _netManager.RegisterNetMessage(_msgReqData); _netManager.RegisterNetMessage(_msgModifyRemote); _netManager.RegisterNetMessage(_msgCloseSession); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); } 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)) { Logger.WarningS("vv", $"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.CanViewVar(player)) { Deny(ViewVariablesResponseCode.NoAccess); return; } object theObject; switch (message.Selector) { case ViewVariablesComponentSelector componentSelector: { var compType = _reflectionManager.GetType(componentSelector.ComponentType); if (compType == null || !_entityManager.TryGetComponent(componentSelector.Entity, compType, out var component)) { Deny(ViewVariablesResponseCode.NoObject); return; } theObject = component; break; } case ViewVariablesEntitySelector entitySelector: { if (!_entityManager.EntityExists(entitySelector.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) { Logger.ErrorS("vv", "Exception while retrieving value for session. {0}", e); Deny(ViewVariablesResponseCode.NoObject); return; } if (value == null || value.GetType().IsValueType) { Deny(ViewVariablesResponseCode.NoObject); return; } theObject = value; 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, sessionId, this, _robustSerializer); _sessions.Add(sessionId, session); var allowMsg = new MsgViewVariablesOpenSession(); allowMsg.RequestId = message.RequestId; allowMsg.SessionId = session.SessionId; _netManager.ServerSendMessage(allowMsg, message.MsgChannel); player.PlayerStatusChanged += (_, args) => { if (args.NewStatus == SessionStatus.Disconnected) { _closeSession(session.SessionId, false); } }; } 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.ConnectedClient); } 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) { return _playerManager.TryGetSessionByChannel(channel, out var session) && _groupController.CanViewVar(session); } protected override bool TryGetSession(Guid guid, [NotNullWhen(true)] out ICommonSession? session) { if (guid != Guid.Empty && _playerManager.TryGetSessionById(new NetUserId(guid), out var player) && !_groupController.CanViewVar(player)) // Can't VV other admins. { session = player; return true; } session = null; return false; } } }