amogus
This commit is contained in:
@@ -1,256 +0,0 @@
|
|||||||
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"
|
|
||||||
];
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeNetworkEvent<CheatCheckRequestEvent>(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(),
|
|
||||||
HasHarmony = CheckForHarmony(),
|
|
||||||
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 CheckForHarmony()
|
|
||||||
{
|
|
||||||
var harmonyType = Type.GetType("HarmonyLib.Harmony, 0Harmony");
|
|
||||||
return harmonyType != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool FoundExtraTypesIReflection([NotNullWhen(true)] out string? offender)
|
|
||||||
{
|
|
||||||
offender = null;
|
|
||||||
string[] typenames = ["SubverterPatch", "MarseyPatch", "MarseyEntry", "Sedition", "Ware"];
|
|
||||||
|
|
||||||
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",
|
|
||||||
"visuals",
|
|
||||||
"esp",
|
|
||||||
"noslip",
|
|
||||||
"exploit",
|
|
||||||
"fun",
|
|
||||||
"scan",
|
|
||||||
];
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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<ScreengrabRequestEvent>(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<Rgb24> image)
|
|
||||||
{
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
image.SaveAsJpeg(stream);
|
|
||||||
return stream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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<NyaGrabSystem>();
|
|
||||||
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<CheatCheckSystem>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
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;
|
|
||||||
using Content.Server.GameTicking;
|
|
||||||
using Content.Shared._White;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
|
|
||||||
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!;
|
|
||||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
|
||||||
[Dependency] private readonly CheatCheckSystem _cheatCheckSystem = default!;
|
|
||||||
|
|
||||||
private readonly Dictionary<ICommonSession, PendingReply> _pendingReplies = new();
|
|
||||||
|
|
||||||
private const float ReplyTimeoutSeconds = 6.0f;
|
|
||||||
private readonly HttpClient _httpClient = new();
|
|
||||||
|
|
||||||
private string _webhookUrl = "";
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
_playMan.PlayerStatusChanged += OnPlayerStatusChanged;
|
|
||||||
|
|
||||||
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
|
|
||||||
|
|
||||||
_configuration.OnValueChanged(WhiteCVars.ACWebhook, s => _webhookUrl = s, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev)
|
|
||||||
{
|
|
||||||
_cheatCheckSystem.RequestCheck(ev.PlayerSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
|
||||||
{
|
|
||||||
if (e is { OldStatus: SessionStatus.InGame, NewStatus: SessionStatus.Disconnected })
|
|
||||||
{
|
|
||||||
if (_pendingReplies.ContainsKey(e.Session))
|
|
||||||
{
|
|
||||||
var warningMsg = $"Игрок отключился во время ожидания ответа! Nya должен был получить: {_pendingReplies[e.Session].Request.ExpectedReplyType}.";
|
|
||||||
SendSuspiciousActivityAlert(e.Session, warningMsg, 80);
|
|
||||||
_pendingReplies.Remove(e.Session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExpectReply<TRequest, TResponse>(
|
|
||||||
ICommonSession player,
|
|
||||||
TRequest request,
|
|
||||||
Action<TResponse, EntitySessionEventArgs> 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<ICommonSession>();
|
|
||||||
|
|
||||||
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} секунд. Будьте бдительны с этим игроком! Nya советует использовать nyagrab!";
|
|
||||||
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 = $"[Anticheat] Внимание! Подозрительная активность:\n" +
|
|
||||||
$"Игрок {player.Name} возможно читер!\n" +
|
|
||||||
$"Причина обнаружения: {reason}";
|
|
||||||
|
|
||||||
_chatManager.SendAdminAnnouncement(inGameMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown()
|
|
||||||
{
|
|
||||||
base.Shutdown();
|
|
||||||
_playMan.PlayerStatusChanged -= OnPlayerStatusChanged;
|
|
||||||
_pendingReplies.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
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 Content.Shared._White;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
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!;
|
|
||||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
|
||||||
|
|
||||||
private readonly HttpClient _httpClient = new();
|
|
||||||
|
|
||||||
private string _webhookUrl = "";
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeNetworkEvent<CheatCheckResponseEvent>(OnCheckResponse);
|
|
||||||
|
|
||||||
_configuration.OnValueChanged(WhiteCVars.ACWebhook, s => _webhookUrl = s, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RequestCheck(ICommonSession player)
|
|
||||||
{
|
|
||||||
_expectedReply.ExpectReply<CheatCheckRequestEvent, CheatCheckResponseEvent>(
|
|
||||||
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.HasHarmony)
|
|
||||||
detections.Add(("Чит-клиент", "Имеется 0Harmony. Возможны читы/патчи. Будьте бдительны!", 70));
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
var cleanMsg = $"✅ **Античит завершил проверку**\n\n" +
|
|
||||||
$"**Игрок:** {args.SenderSession.Name}\n" +
|
|
||||||
$"**IP:** {args.SenderSession.Channel.RemoteEndPoint}\n" +
|
|
||||||
$"**Результат:** Нарушений не выявлено";
|
|
||||||
|
|
||||||
var cleanEmbed = new
|
|
||||||
{
|
|
||||||
title = "✅ Проверка завершена",
|
|
||||||
description = cleanMsg,
|
|
||||||
color = 0x00FF00, // Зеленый
|
|
||||||
timestamp = DateTime.UtcNow.ToString("o")
|
|
||||||
};
|
|
||||||
|
|
||||||
var cleanPayload = new
|
|
||||||
{
|
|
||||||
embeds = new[] { cleanEmbed }
|
|
||||||
};
|
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(cleanPayload);
|
|
||||||
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 cleanInGameMsg = $"[Anticheat] Проверка завершена\n" +
|
|
||||||
$"Игрок: {args.SenderSession.Name}\n" +
|
|
||||||
$"Результат: Нарушений не выявлено";
|
|
||||||
|
|
||||||
_chatManager.SendAdminAnnouncement(cleanInGameMsg);
|
|
||||||
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 jsonA = JsonSerializer.Serialize(payload);
|
|
||||||
var contentA = new StringContent(jsonA, Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _httpClient.PostAsync(_webhookUrl, contentA);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error($"Failed to send Discord webhook: {e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var inGameMsg = $"[Anticheat] Обнаружена подозрительная активность!\n" +
|
|
||||||
$"Игрок: {args.SenderSession.Name}\n" +
|
|
||||||
$"Вероятность использования читов: {totalSeverity}%\n" +
|
|
||||||
$"Обнаруженные нарушения:";
|
|
||||||
|
|
||||||
foreach (var (type, details, severity) in detections)
|
|
||||||
{
|
|
||||||
inGameMsg += $"\n•{type} ({severity}%): {details}";
|
|
||||||
}
|
|
||||||
|
|
||||||
_chatManager.SendAdminAnnouncement(inGameMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
|
||||||
using Content.Shared._Miracle.Nya;
|
|
||||||
using Content.Shared._White;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
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!;
|
|
||||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
|
||||||
|
|
||||||
private readonly HttpClient _httpClient = new();
|
|
||||||
|
|
||||||
private string _webhookUrl = "";
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeNetworkEvent<ScreengrabResponseEvent>(OnScreengrabResponse);
|
|
||||||
|
|
||||||
_configuration.OnValueChanged(WhiteCVars.ACWebhook, s => _webhookUrl = s, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RequestScreengrab(ICommonSession player)
|
|
||||||
{
|
|
||||||
_expectedReply.ExpectReply<ScreengrabRequestEvent, ScreengrabResponseEvent>(
|
|
||||||
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<Rgb24>(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 embed = new
|
|
||||||
{
|
|
||||||
title = "📸 Скриншот игрока",
|
|
||||||
description = $"**Игрок**: {args.SenderSession.Name}\n" +
|
|
||||||
$"**UserId**: {args.SenderSession.UserId}\n" +
|
|
||||||
$"**IP**: {args.SenderSession.Channel.RemoteEndPoint}\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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using Content.Shared._Miracle.Nya;
|
|
||||||
|
|
||||||
namespace Content.Server._Miracle.Nya;
|
|
||||||
|
|
||||||
public sealed class PendingReply(
|
|
||||||
ExpectedReplyEntityEventArgs request,
|
|
||||||
TimeSpan timeout,
|
|
||||||
Action<EntityEventArgs, EntitySessionEventArgs> handler)
|
|
||||||
{
|
|
||||||
public ExpectedReplyEntityEventArgs Request { get; } = request;
|
|
||||||
public TimeSpan TimeoutTime { get; } = timeout;
|
|
||||||
public Action<EntityEventArgs, EntitySessionEventArgs> Handler { get; } = handler;
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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 bool HasHarmony;
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
@@ -420,7 +420,4 @@ public sealed class WhiteCVars
|
|||||||
public static readonly CVarDef<float> ItemToArtifactRatio =
|
public static readonly CVarDef<float> ItemToArtifactRatio =
|
||||||
CVarDef.Create("white.random_artifacts_ratio", 0.5f, CVar.SERVERONLY);
|
CVarDef.Create("white.random_artifacts_ratio", 0.5f, CVar.SERVERONLY);
|
||||||
|
|
||||||
public static readonly CVarDef<string> ACWebhook =
|
|
||||||
CVarDef.Create("ac.webhook", "", CVar.SERVERONLY);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user