From 2b05807882b25d587798425f06e1ba87b40eb28f Mon Sep 17 00:00:00 2001 From: haiwwkes <49613070+rhailrake@users.noreply.github.com> Date: Mon, 28 Oct 2024 04:22:52 +0500 Subject: [PATCH] the fuck (#752) * the fuck * more logs --- Content.Client/_Miracle/Nya/NyaCheckSystem.cs | 247 ++++++++++++++++++ Content.Client/_Miracle/Nya/NyaGrabSystem.cs | 38 +++ Content.Server/_Miracle/Nya/Commands.cs | 82 ++++++ .../_Miracle/Nya/ExpectedReplySystem.cs | 163 ++++++++++++ Content.Server/_Miracle/Nya/NyaCheckSystem.cs | 134 ++++++++++ Content.Server/_Miracle/Nya/NyaGrabSystem.cs | 90 +++++++ Content.Server/_Miracle/Nya/PendingReply.cs | 13 + .../_Miracle/Nya/NyaSerializable.cs | 44 ++++ 8 files changed, 811 insertions(+) create mode 100644 Content.Client/_Miracle/Nya/NyaCheckSystem.cs create mode 100644 Content.Client/_Miracle/Nya/NyaGrabSystem.cs create mode 100644 Content.Server/_Miracle/Nya/Commands.cs create mode 100644 Content.Server/_Miracle/Nya/ExpectedReplySystem.cs create mode 100644 Content.Server/_Miracle/Nya/NyaCheckSystem.cs create mode 100644 Content.Server/_Miracle/Nya/NyaGrabSystem.cs create mode 100644 Content.Server/_Miracle/Nya/PendingReply.cs create mode 100644 Content.Shared/_Miracle/Nya/NyaSerializable.cs diff --git a/Content.Client/_Miracle/Nya/NyaCheckSystem.cs b/Content.Client/_Miracle/Nya/NyaCheckSystem.cs new file mode 100644 index 0000000000..5c4e1723f8 --- /dev/null +++ b/Content.Client/_Miracle/Nya/NyaCheckSystem.cs @@ -0,0 +1,247 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared._Miracle.Nya; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Shared.Configuration; +using Robust.Shared.Reflection; + +namespace Content.Client._Miracle.Nya; + +public sealed class NyaCheckClientSystem : EntitySystem +{ + [Dependency] private readonly IReflectionManager _reflection = default!; + [Dependency] private readonly IConfigurationManager _configuration = default!; + [Dependency] private readonly IEntitySystemManager _esm = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IUserInterfaceManager _ui = default!; + + [ViewVariables(VVAccess.ReadOnly)] + private readonly string[] _allowed = + [ + "Content.Client", + "Content.Shared", + "Content.Server", + "Content.Shared.Database", + "Robust.Client", + "Robust.Shared", + "Robust.Server", + "Content.Anticheat", + ]; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnCheckRequest); + } + + private void OnCheckRequest(CheatCheckRequestEvent ev) + { + var response = RunChecks(); + RaiseNetworkEvent(response); + } + + private CheatCheckResponseEvent RunChecks() + { + return new CheatCheckResponseEvent + { + HasPatchMetadata = FoundPatchMetadataTypes(), + ReflectionOffender = FoundExtraTypesIReflection(out var reflectionOffender) ? reflectionOffender : null, + HasMoonyware = FoundMoonywareModuleReflection(), + IoCOffender = TypesNotFromContentIoC(out var iocOffender) ? iocOffender : null, + ExtraModuleOffender = CheckExtraModule(out var moduleOffender) ? moduleOffender : null, + CvarOffender = CheckCommonCheatCvars(out var cvarOffender) ? cvarOffender : null, + SystemOffender = FoundTypesEntitySystemManager(out var systemOffender) ? systemOffender : null, + ComponentOffender = CheckComponents(out var componentOffender) ? componentOffender : null, + WindowOffender = CheckExtraWindows(out var windowOffender) ? windowOffender : null + }; + } + + private bool FoundPatchMetadataTypes() + { + var found = Type.GetType("MarseyPatch") ?? Type.GetType("SubverterPatch"); + return found is not null; + } + + private bool FoundExtraTypesIReflection([NotNullWhen(true)] out string? offender) + { + offender = null; + string[] typenames = ["SubverterPatch", "MarseyPatch", "MarseyEntry", "Sedition"]; + + var types = _reflection.FindAllTypes(); + + foreach (var type in types) + { + foreach (var name in typenames) + { + if (!type.Name.Contains(name)) + continue; + + offender = type.Name; + return true; + } + } + + return false; + } + + private bool FoundMoonywareModuleReflection() + { + var modules = _reflection.Assemblies; + + foreach (var asm in modules) + { + if (asm.FullName!.Contains("Moonyware")) + return true; + } + + return false; + } + + private bool FoundTypesEntitySystemManager([NotNullWhen(true)] out string? offend) + { + offend = null; + + var types = _esm.GetEntitySystemTypes(); + + foreach (var type in types) + { + if (!NotFromGameModule(type)) + continue; + + offend = type.FullName!; + return true; + } + + return false; + } + + private bool TypesNotFromContentIoC([NotNullWhen(true)] out string? offend) + { + offend = null; + + var types = IoCManager.Instance!.GetRegisteredTypes(); + + foreach (var type in types) + { + if (!NotFromGameModule(type)) + continue; + + offend = type.FullName!; + return true; + } + + return false; + } + + private bool CheckExtraModule([NotNullWhen(true)] out string? offend) + { + offend = null; + + var modules = _reflection.Assemblies; + + foreach (var module in modules) + { + var allowed = false; + + foreach (var allow in _allowed) + { + if (module.FullName!.Contains(allow)) + { + allowed = true; + break; + } + } + + if (allowed) + continue; + + offend = module.FullName!; + return true; + } + + return false; + } + + private bool CheckCommonCheatCvars([NotNullWhen(true)] out string? offend) + { + string[] keywords = + [ + "aimbot", + "esp", + "noslip", + "exploit", + ]; + + offend = null; + + var cvars = _configuration.GetRegisteredCVars(); + + foreach (var cvar in cvars) + { + if (!keywords.Any(kw => cvar.Contains(kw, StringComparison.CurrentCultureIgnoreCase))) + continue; + + offend = cvar; + return true; + } + + return false; + } + + private bool CheckComponents([NotNullWhen(true)] out string? offend) + { + offend = null; + + if (_player.LocalEntity is null) + return false; + + var comps = AllComps(_player.LocalEntity.Value); + + foreach (var comp in comps) + { + var type = comp.GetType(); + + if (!NotFromGameModule(type)) + continue; + + offend = type.FullName!; + return true; + } + + return false; + } + + private bool CheckExtraWindows([NotNullWhen(true)] out string? offend) + { + offend = null; + + var children = _ui.WindowRoot.Children; + + foreach (var child in children) + { + var type = child.GetType(); + + if (!NotFromGameModule(type)) + continue; + + offend = type.FullName!; + return true; + } + + return false; + } + + private bool NotFromGameModule(Type type) + { + var name = type.FullName; + + foreach (var allow in _allowed) + { + if (name!.Contains(allow)) + return false; + } + + return true; + } +} diff --git a/Content.Client/_Miracle/Nya/NyaGrabSystem.cs b/Content.Client/_Miracle/Nya/NyaGrabSystem.cs new file mode 100644 index 0000000000..836b355ee7 --- /dev/null +++ b/Content.Client/_Miracle/Nya/NyaGrabSystem.cs @@ -0,0 +1,38 @@ +using System.IO; +using Content.Shared._Miracle.Nya; +using Robust.Client.Graphics; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace Content.Client._Miracle.Nya; + +public sealed class NyaGrabSystem : EntitySystem +{ + [Dependency] private readonly IClyde _clyde = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnScreengrabRequest); + } + + private async void OnScreengrabRequest(ScreengrabRequestEvent ev) + { + var image = await _clyde.ScreenshotAsync(ScreenshotType.Final); + var array = ImageToByteArray(image); + + if (array.Length > 1_500_000) + return; + + var msg = new ScreengrabResponseEvent { Screengrab = array }; + RaiseNetworkEvent(msg); + } + + private byte[] ImageToByteArray(Image image) + { + using var stream = new MemoryStream(); + image.SaveAsJpeg(stream); + return stream.ToArray(); + } +} diff --git a/Content.Server/_Miracle/Nya/Commands.cs b/Content.Server/_Miracle/Nya/Commands.cs new file mode 100644 index 0000000000..e3d317e9c7 --- /dev/null +++ b/Content.Server/_Miracle/Nya/Commands.cs @@ -0,0 +1,82 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Shared.Administration; +using Robust.Server.Player; +using Robust.Shared.Console; + +namespace Content.Server._Miracle.Nya; + +[AdminCommand(AdminFlags.Admin)] +public sealed class ScreenGrabCommand : IConsoleCommand +{ + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + public string Command => "nyagrab"; + public string Description => "nyagrab!!"; + public string Help => "nyagrab player"; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var sys = _entityManager.EntitySysManager.GetEntitySystem(); + if (args.Length < 1) + { + var player = shell.Player; + var toKickPlayer = player ?? _players.Sessions.FirstOrDefault(); + if (toKickPlayer == null) + { + shell.WriteLine("You need to provide a player to nyagrab."); + return; + } + + shell.WriteLine( + $"You need to provide a player to nyagrab. Try running 'nyagrab {toKickPlayer.Name}' as an example."); + return; + } + + var name = args[0]; + + if (_players.TryGetSessionByUsername(name, out var target)) + { + sys.RequestScreengrab(target); + } + } +} + +[AdminCommand(AdminFlags.Admin)] +public sealed class NyaCheckCommand : IConsoleCommand +{ + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + public string Command => "nyacheck"; + public string Description => "nyacheck!!"; + public string Help => "nyacheck player"; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var sys = _entityManager.EntitySysManager.GetEntitySystem(); + + if (args.Length < 1) + { + var player = shell.Player; + var toKickPlayer = player ?? _players.Sessions.FirstOrDefault(); + if (toKickPlayer == null) + { + shell.WriteLine("You need to provide a player to nyacheck."); + return; + } + + shell.WriteLine( + $"You need to provide a player to nyacheck. Try running 'nyacheck {toKickPlayer.Name}' as an example."); + return; + } + + var name = args[0]; + + if (_players.TryGetSessionByUsername(name, out var target)) + { + sys.RequestCheck(target); + } + } +} diff --git a/Content.Server/_Miracle/Nya/ExpectedReplySystem.cs b/Content.Server/_Miracle/Nya/ExpectedReplySystem.cs new file mode 100644 index 0000000000..966304a8ac --- /dev/null +++ b/Content.Server/_Miracle/Nya/ExpectedReplySystem.cs @@ -0,0 +1,163 @@ +using Content.Server.Chat.Managers; +using Robust.Shared.Player; +using Content.Shared._Miracle.Nya; +using Robust.Shared.Enums; +using Robust.Shared.Timing; +using System.Net.Http; +using System.Text; +using System.Text.Json; + +namespace Content.Server._Miracle.Nya; + +public sealed class ExpectedReplySystem : EntitySystem +{ + [Dependency] private readonly ISharedPlayerManager _playMan = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + + private readonly Dictionary _pendingReplies = new(); + + private const float ReplyTimeoutSeconds = 5.0f; + private readonly HttpClient _httpClient = new(); + + private const string WebhookUrl = "https://discord.com/api/webhooks/1300204694395945021/jO_2nmXDXfMm2hKHH019gk1HqujhcHlW8yfmyMBeuScaOvCOiRJK9XurSJLf6AxpHmRv"; + + public override void Initialize() + { + base.Initialize(); + _playMan.PlayerStatusChanged += OnPlayerStatusChanged; + } + + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e is { OldStatus: SessionStatus.InGame, NewStatus: SessionStatus.Disconnected }) + { + if (_pendingReplies.ContainsKey(e.Session)) + { + var warningMsg = $"Игрок отключился во время ожидания ответа!"; + SendSuspiciousActivityAlert(e.Session, warningMsg, 80); + _pendingReplies.Remove(e.Session); + } + } + } + + public void ExpectReply( + ICommonSession player, + TRequest request, + Action handler) + where TRequest : ExpectedReplyEntityEventArgs + where TResponse : EntityEventArgs + { + var timeout = _timing.CurTime + TimeSpan.FromSeconds(ReplyTimeoutSeconds); + + void WrapHandler(EntityEventArgs ev, EntitySessionEventArgs args) + { + if (ev is TResponse response) + handler(response, args); + } + + _pendingReplies[player] = new PendingReply(request, timeout, WrapHandler); + RaiseNetworkEvent(request, player.Channel); + } + + public bool HandleReply(EntityEventArgs ev, EntitySessionEventArgs args) + { + if (!_pendingReplies.TryGetValue(args.SenderSession, out var pending)) + { + var warningMsg = "Получен неожиданный ответ без запроса"; + SendSuspiciousActivityAlert(args.SenderSession, warningMsg, 70); + return false; + } + + if (pending.Request.ExpectedReplyType != ev.GetType()) + { + var warningMsg = $"Получен ответ неверного типа. Ожидался {pending.Request.ExpectedReplyType}, получен {ev.GetType()}"; + SendSuspiciousActivityAlert(args.SenderSession, warningMsg, 75); + return false; + } + + pending.Handler(ev, args); + _pendingReplies.Remove(args.SenderSession); + return true; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var currentTime = _timing.CurTime; + var timeoutPlayers = new List(); + + foreach (var (player, pending) in _pendingReplies) + { + if (currentTime > pending.TimeoutTime) + timeoutPlayers.Add(player); + } + + foreach (var player in timeoutPlayers) + { + HandleTimeout(player); + _pendingReplies.Remove(player); + } + } + + private void HandleTimeout(ICommonSession player) + { + var warningMsg = $"Не получен ответ в течение {ReplyTimeoutSeconds} секунд"; + SendSuspiciousActivityAlert(player, warningMsg, 65); + } + + private async void SendSuspiciousActivityAlert(ICommonSession player, string reason, int severity) + { + var color = severity switch + { + >= 80 => 0xFF0000, // Красный + >= 70 => 0xFFA500, // Оранжевый + _ => 0xFFFF00 // Желтый + }; + + var warningMsg = $"⚠️ **Система ожидаемых ответов обнаружила подозрительную активность!**\n\n" + + $"**Игрок:** {player.Name}\n" + + $"**IP:** {player.Channel.RemoteEndPoint}\n" + + $"**Уровень подозрительности:** {severity}%\n" + + $"**Причина:** {reason}"; + + var embed = new + { + title = "⚠️ Подозрительная активность!", + description = warningMsg, + color = color, + timestamp = DateTime.UtcNow.ToString("o") + }; + + var payload = new + { + embeds = new[] { embed } + }; + + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + try + { + await _httpClient.PostAsync(WebhookUrl, content); + } + catch (Exception e) + { + Log.Error($"Failed to send Discord webhook: {e}"); + } + + var inGameMsg = $"[color=red][Anticheat][/color] Внимание! Подозрительная активность:\n" + + $"Игрок {player.Name} возможно читер!\n" + + $"Причина обнаружения: {reason}"; + + _chatManager.SendAdminAnnouncement(inGameMsg); + } + + public override void Shutdown() + { + base.Shutdown(); + _playMan.PlayerStatusChanged -= OnPlayerStatusChanged; + _pendingReplies.Clear(); + } +} diff --git a/Content.Server/_Miracle/Nya/NyaCheckSystem.cs b/Content.Server/_Miracle/Nya/NyaCheckSystem.cs new file mode 100644 index 0000000000..de94e676bf --- /dev/null +++ b/Content.Server/_Miracle/Nya/NyaCheckSystem.cs @@ -0,0 +1,134 @@ +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using Content.Server.Chat.Managers; +using Content.Shared._Miracle.Nya; +using Robust.Shared.Player; + +namespace Content.Server._Miracle.Nya; + +public sealed class CheatCheckSystem : EntitySystem +{ + [Dependency] private readonly ExpectedReplySystem _expectedReply = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + + private readonly HttpClient _httpClient = new(); + + private const string WebhookUrl = "https://discord.com/api/webhooks/1300204694395945021/jO_2nmXDXfMm2hKHH019gk1HqujhcHlW8yfmyMBeuScaOvCOiRJK9XurSJLf6AxpHmRv"; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnCheckResponse); + } + + public void RequestCheck(ICommonSession player) + { + _expectedReply.ExpectReply( + player, + new CheatCheckRequestEvent(), + ProcessCheckResponse + ); + } + + private void OnCheckResponse(CheatCheckResponseEvent ev, EntitySessionEventArgs args) + { + if (!_expectedReply.HandleReply(ev, args)) + return; + } + + private async void ProcessCheckResponse(CheatCheckResponseEvent ev, EntitySessionEventArgs args) + { + var detections = new List<(string Type, string Details, int Severity)>(); + + if (ev.HasPatchMetadata) + detections.Add(("Инъекция кода", "Обнаружены метаданные патча", 90)); + + if (ev.ReflectionOffender != null) + detections.Add(("Рефлексия", $"Найден подозрительный тип: {ev.ReflectionOffender}", 80)); + + if (ev.HasMoonyware) + detections.Add(("Чит-клиент", "Обнаружен Moonyware", 95)); + + if (ev.IoCOffender != null) + detections.Add(("IoC манипуляция", $"Неразрешенный тип: {ev.IoCOffender}", 70)); + + if (ev.ExtraModuleOffender != null) + detections.Add(("Внешний модуль", $"Неразрешенный модуль: {ev.ExtraModuleOffender}", 85)); + + if (ev.CvarOffender != null) + detections.Add(("Подозрительный CVar", $"Найден чит-квар: {ev.CvarOffender}", 60)); + + if (ev.SystemOffender != null) + detections.Add(("Системное вмешательство", $"Неразрешенная система: {ev.SystemOffender}", 75)); + + if (ev.ComponentOffender != null) + detections.Add(("Компонентное вмешательство", $"Неразрешенный компонент: {ev.ComponentOffender}", 75)); + + if (ev.WindowOffender != null) + detections.Add(("UI вмешательство", $"Неразрешенное окно: {ev.WindowOffender}", 65)); + + if (detections.Count == 0) + return; + + var maxSeverity = detections.Max(d => d.Severity); + var avgSeverity = detections.Average(d => d.Severity); + var totalSeverity = (int)((maxSeverity * 0.7) + (avgSeverity * 0.3)); + + var warningMsg = $"🚨 **Античит обнаружил подозрительную активность!**\n\n" + + $"**Игрок:** {args.SenderSession.Name}\n" + + $"**IP:** {args.SenderSession.Channel.RemoteEndPoint}\n" + + $"**Вероятность использования читов:** {totalSeverity}%\n\n" + + $"**Обнаруженные нарушения:**\n"; + + foreach (var (type, details, severity) in detections) + { + warningMsg += $"• **{type}** ({severity}%): {details}\n"; + } + + var color = totalSeverity switch + { + >= 90 => 0xFF0000, // Красный + >= 70 => 0xFFA500, // Оранжевый + _ => 0xFFFF00 // Желтый + }; + + var embed = new + { + title = "🚫 Обнаружен читер!", + description = warningMsg, + color = color, + timestamp = DateTime.UtcNow.ToString("o") + }; + + var payload = new + { + embeds = new[] { embed } + }; + + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + try + { + await _httpClient.PostAsync(WebhookUrl, content); + } + catch (Exception e) + { + Log.Error($"Failed to send Discord webhook: {e}"); + } + + var inGameMsg = $"[color=red][Anticheat][/color] Обнаружена подозрительная активность!\n" + + $"Игрок: {args.SenderSession.Name}\n" + + $"Вероятность использования читов: {totalSeverity}%\n" + + $"Обнаруженные нарушения:"; + + foreach (var (type, details, severity) in detections) + { + inGameMsg += $"\n[color=yellow]• {type}[/color] ({severity}%): {details}"; + } + + _chatManager.SendAdminAnnouncement(inGameMsg); + } +} diff --git a/Content.Server/_Miracle/Nya/NyaGrabSystem.cs b/Content.Server/_Miracle/Nya/NyaGrabSystem.cs new file mode 100644 index 0000000000..5bc337dee5 --- /dev/null +++ b/Content.Server/_Miracle/Nya/NyaGrabSystem.cs @@ -0,0 +1,90 @@ +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using Content.Shared._Miracle.Nya; +using Robust.Shared.Player; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace Content.Server._Miracle.Nya; + +public sealed class NyaGrabSystem : EntitySystem +{ + [Dependency] private readonly ExpectedReplySystem _expectedReply = default!; + + private readonly HttpClient _httpClient = new(); + + private const string WebhookUrl = "https://discord.com/api/webhooks/1300204694395945021/jO_2nmXDXfMm2hKHH019gk1HqujhcHlW8yfmyMBeuScaOvCOiRJK9XurSJLf6AxpHmRv"; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnScreengrabResponse); + } + + public void RequestScreengrab(ICommonSession player) + { + _expectedReply.ExpectReply( + player, + new ScreengrabRequestEvent(), + OnScreengrabReply + ); + } + + private void OnScreengrabResponse(ScreengrabResponseEvent ev, EntitySessionEventArgs args) + { + if (!_expectedReply.HandleReply(ev, args)) + return; + } + + private async void OnScreengrabReply(ScreengrabResponseEvent ev, EntitySessionEventArgs args) + { + if (ev.Screengrab.Length == 0) + return; + + var timestamp = DateTime.UtcNow; + var imagedata = ev.Screengrab; + using var image = Image.Load(imagedata); + + var content = new MultipartFormDataContent(); + + var fileName = $"screengrab_{args.SenderSession.UserId}_{timestamp:yyyy-MM-dd_HH-mm-ss}.jpg"; + var fileContent = new ByteArrayContent(imagedata); + fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg"); + content.Add(fileContent, "file", fileName); + + var hwIdString = string.Join("", args.SenderSession.Channel.UserData.HWId.Select(b => b.ToString("X2"))); + + var embed = new + { + title = "📸 Скриншот игрока", + description = $"**Игрок**: {args.SenderSession.Name}\n" + + $"**UserId**: {args.SenderSession.UserId}\n" + + $"**IP**: {args.SenderSession.Channel.RemoteEndPoint}\n" + + $"**HWId**: {hwIdString}\n" + + $"**Дата и время**: {timestamp:yyyy-MM-dd HH:mm:ss} UTC\n" + + $"**Разрешение**: {image.Width}x{image.Height}\n" + + $"**Размер**: {(imagedata.Length / 1024.0):F2} KB", + color = 0x00FF00, + timestamp = timestamp.ToString("o") + }; + + var payload = new + { + embeds = new[] { embed } + }; + + var jsonContent = JsonSerializer.Serialize(payload); + content.Add(new StringContent(jsonContent), "payload_json"); + + try + { + await _httpClient.PostAsync(WebhookUrl, content); + Log.Info($"Screenshot sent to Discord for player {args.SenderSession.Name}"); + } + catch (Exception e) + { + Log.Error($"Failed to send screenshot to Discord: {e}"); + } + } +} diff --git a/Content.Server/_Miracle/Nya/PendingReply.cs b/Content.Server/_Miracle/Nya/PendingReply.cs new file mode 100644 index 0000000000..7ed69ad3f0 --- /dev/null +++ b/Content.Server/_Miracle/Nya/PendingReply.cs @@ -0,0 +1,13 @@ +using Content.Shared._Miracle.Nya; + +namespace Content.Server._Miracle.Nya; + +public sealed class PendingReply( + ExpectedReplyEntityEventArgs request, + TimeSpan timeout, + Action handler) +{ + public ExpectedReplyEntityEventArgs Request { get; } = request; + public TimeSpan TimeoutTime { get; } = timeout; + public Action Handler { get; } = handler; +} diff --git a/Content.Shared/_Miracle/Nya/NyaSerializable.cs b/Content.Shared/_Miracle/Nya/NyaSerializable.cs new file mode 100644 index 0000000000..d77e11d4bb --- /dev/null +++ b/Content.Shared/_Miracle/Nya/NyaSerializable.cs @@ -0,0 +1,44 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Miracle.Nya; + +[Serializable, NetSerializable] +public sealed class ScreengrabResponseEvent : EntityEventArgs +{ + public byte[] Screengrab = new byte[1500000]; // Limit screengrab size to 1.5mbs +} + +[Serializable, NetSerializable] +public sealed class ScreengrabRequestEvent : ExpectedReplyEntityEventArgs +{ + [field: NonSerialized] + public override Type ExpectedReplyType { get; } = typeof(ScreengrabResponseEvent); +} + +[Serializable, NetSerializable] +public sealed class CheatCheckResponseEvent : EntityEventArgs +{ + public bool HasPatchMetadata; + public string? ReflectionOffender; + public bool HasMoonyware; + public string? IoCOffender; + public string? ExtraModuleOffender; + public string? CvarOffender; + public string? SystemOffender; + public string? ComponentOffender; + public string? WindowOffender; +} + +[Serializable, NetSerializable] +public sealed class CheatCheckRequestEvent : ExpectedReplyEntityEventArgs +{ + [field: NonSerialized] + public override Type ExpectedReplyType { get; } = typeof(CheatCheckResponseEvent); +} + +[Serializable, NetSerializable] +public abstract class ExpectedReplyEntityEventArgs : EntityEventArgs +{ + [field: NonSerialized] + public abstract Type ExpectedReplyType { get; } +}