AdminStealth/AdminStealth.cs

120 lines
4.1 KiB
C#

using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
namespace AdminStealth;
// Companion to CS2-SimpleAdmin's css_hide. When an admin hides, everyone else sees them leave the
// server, and nothing the hide does afterwards (the suicide, the team changes, their real
// disconnect later) is announced. Also blocks the status command for players, which would
// otherwise still list a hidden admin.
public class AdminStealth : BasePlugin
{
public override string ModuleName => "AdminStealth";
public override string ModuleVersion => "0.1.0";
public override string ModuleAuthor => "astra";
public override string ModuleDescription => "Makes CS2-SimpleAdmin's css_hide look like leaving the server";
private static readonly PluginCapability<ICS2_SimpleAdminApi> SimpleAdminCapability = new("simpleadmin:api");
private ICS2_SimpleAdminApi? _simpleAdmin;
public override void Load(bool hotReload)
{
AddCommandListener("status", OnStatus);
}
public override void OnAllPluginsLoaded(bool hotReload)
{
try
{
_simpleAdmin = SimpleAdminCapability.Get();
}
catch (KeyNotFoundException)
{
}
if (_simpleAdmin == null)
{
Logger.LogError("CS2-SimpleAdmin's API isn't available, so hiding won't look like leaving");
return;
}
_simpleAdmin.OnAdminToggleSilent += OnAdminToggleSilent;
}
public override void Unload(bool hotReload)
{
if (_simpleAdmin != null)
_simpleAdmin.OnAdminToggleSilent -= OnAdminToggleSilent;
}
// Players get nothing back. The server console and RCON still work.
private HookResult OnStatus(CCSPlayerController? player, CommandInfo command)
{
return player == null ? HookResult.Continue : HookResult.Stop;
}
private void OnAdminToggleSilent(int slot, bool hidden)
{
if (!hidden)
return;
var admin = Utilities.GetPlayerFromSlot(slot);
if (admin is not { IsValid: true, IsBot: false })
return;
// A fake player_disconnect, sent to each client rather than fired on the server, so no
// plugin (SimpleAdmin included) treats the admin as really gone.
var leave = new EventPlayerDisconnect(true)
{
Userid = admin,
Playerid = admin.Slot,
Name = admin.PlayerName,
Networkid = new SteamID(admin.SteamID).SteamId3,
Xuid = admin.SteamID,
Reason = (int)NetworkDisconnectionReason.NETWORK_DISCONNECT_DISCONNECT_BY_USER,
EverFullyConnected = true,
};
foreach (var player in Utilities.GetPlayers())
{
if (player is { IsValid: true, IsBot: false, IsHLTV: false } && player.Slot != slot)
leave.FireEventToClient(player);
}
leave.Free();
}
[GameEventHandler(HookMode.Pre)]
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
{
if (IsHidden(@event.Userid))
info.DontBroadcast = true;
return HookResult.Continue;
}
[GameEventHandler(HookMode.Pre)]
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
{
if (IsHidden(@event.Userid))
info.DontBroadcast = true;
return HookResult.Continue;
}
// Pre runs before SimpleAdmin's own handler forgets the player was hidden.
[GameEventHandler(HookMode.Pre)]
public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
{
if (IsHidden(@event.Userid))
info.DontBroadcast = true;
return HookResult.Continue;
}
private bool IsHidden(CCSPlayerController? player)
{
return player is { IsValid: true, IsBot: false } && _simpleAdmin?.IsAdminSilent(player) == true;
}
}