using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Robust.Server.Console; using Robust.Server.Player; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.IoC; using Robust.Shared.Network; using Robust.Shared.Network.Transfer; using Robust.Shared.Player; using Robust.Shared.Upload; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Server.Upload; public sealed class NetworkResourcesUploadedEvent { public ICommonSession Session { get; } public ImmutableArray<(ResPath Relative, byte[] Data)> Files { get; } internal NetworkResourcesUploadedEvent(ICommonSession session, ImmutableArray<(ResPath, byte[])> files) { Session = session; Files = files; } } public sealed partial class NetworkResourceManager : SharedNetworkResourceManager { internal const int AckInitial = 1; [Dependency] private IPlayerManager _playerManager = default!; [Dependency] private IServerNetManager _serverNetManager = default!; [Dependency] private IConfigurationManager _cfgManager = default!; [Dependency] private IConGroupController _controller = default!; [Obsolete("Use ResourcesUploaded instead")] public event Action? OnResourceUploaded; public event Action? ResourcesUploaded; [ViewVariables] public bool Enabled { get; private set; } = true; [ViewVariables] public float SizeLimit { get; private set; } internal event Action? AckReceived; internal override void Initialize() { base.Initialize(); TransferManager.RegisterTransferMessage(TransferKeyNetworkDownload); TransferManager.RegisterTransferMessage(TransferKeyNetworkUpload, ReceiveUpload); _cfgManager.OnValueChanged(CVars.ResourceUploadingEnabled, value => Enabled = value, true); _cfgManager.OnValueChanged(CVars.ResourceUploadingLimitMb, value => SizeLimit = value, true); _serverNetManager.RegisterNetMessage(RxAck); } private void RxAck(NetworkResourceAckMessage message) { AckReceived?.Invoke(message.MsgChannel, message.Key); } private async void ReceiveUpload(TransferReceivedEvent transfer) { // Do not allow uploading any new resources if it has been disabled. // Note: Any resources uploaded before being disabled will still be kept and sent. if (!Enabled) { transfer.Channel.Disconnect("Resource upload not enabled."); return; } if (!_playerManager.TryGetSessionByChannel(transfer.Channel, out var session)) { transfer.Channel.Disconnect("Not in-game"); return; } if (!_controller.CanCommand(session, "uploadfile")) { transfer.Channel.Disconnect("Not authorized"); return; } Sawmill.Verbose("Ingesting file uploads from {Session}", session); List<(ResPath Relative, byte[] Data)> ingested; await using (var stream = transfer.DataStream) { ingested = await IngestFileStream(stream); } Sawmill.Verbose("Ingesting file uploads complete, distributing..."); foreach (var channel in _serverNetManager.Channels) { SendToPlayer(channel, ingested); } #pragma warning disable CS0618 // Type or member is obsolete if (OnResourceUploaded != null) { foreach (var (relative, data) in ingested) { OnResourceUploaded?.Invoke(session, new NetworkResourceUploadMessage { MsgChannel = session.Channel, Data = data, RelativePath = relative }); } } #pragma warning restore CS0618 // Type or member is obsolete ResourcesUploaded?.Invoke(new NetworkResourcesUploadedEvent(session, [.. ingested])); } protected override void ValidateUpload(uint size) { if (SizeLimit > 0f && size * BytesToMegabytes > SizeLimit) throw new Exception("File upload too large!"); } internal bool SendToNewUser(INetChannel channel) { var allFiles = ContentRoot.GetAllFiles().ToList(); if (allFiles.Count == 0) return false; SendToPlayer(channel, allFiles, AckInitial); return true; } private async void SendToPlayer(INetChannel channel, List<(ResPath Relative, byte[] Data)> files, int ack = 0) { await using var stream = TransferManager.StartTransfer(channel, new TransferStartInfo { MessageKey = TransferKeyNetworkDownload }); var ackBytes = new byte[4]; BinaryPrimitives.WriteInt32LittleEndian(ackBytes, ack); await stream.WriteAsync(ackBytes); await WriteFileStream(stream, files); } }