Compare commits

..

No commits in common. "main" and "v0.3.12" have entirely different histories.

3 changed files with 18 additions and 79 deletions

View file

@ -23,12 +23,11 @@ namespace AdminStealth;
public class AdminStealth : BasePlugin
{
public override string ModuleName => "AdminStealth";
public override string ModuleVersion => "0.3.15";
public override string ModuleVersion => "0.3.12";
public override string ModuleAuthor => "astra";
public override string ModuleDescription => "Makes CS2-SimpleAdmin's css_hide look like leaving, and keeps it";
// css_hide's own permission, unless admin_overrides.json overrides css_hide (see CanHide).
// Restoring waits for it, since SimpleAdmin loads admins after connect.
// css_hide's own permission. Restoring waits for it, since SimpleAdmin loads admins after connect.
private const string HidePermission = "@css/kick";
private const float RestoreDelay = 2f;
private const int RestoreAttempts = 5;
@ -66,9 +65,6 @@ public class AdminStealth : BasePlugin
// without ever calling us.
foreach (var alias in _hideAliases)
AddCommandListener(alias, (player, command) => OnHideTyped(player, command));
// Players who may not hide get no reply at all, not even CSS's missing-permission one. Only
// a catch-all listener runs before every plugin's command handler, for console and chat.
AddCommandListener(null, OnAnyCommand);
// Joining any team, spectators included, would make SimpleAdmin unhide them.
AddCommandListener("jointeam", (player, command) => OnJoinTeam(player, command));
AddCommandListener("spectate", (player, command) => OnJoinTeam(player, command));
@ -83,7 +79,6 @@ public class AdminStealth : BasePlugin
_hideTyped.Clear();
_restoreScheduled.Clear();
_restoring.Clear();
_closingTeamScreen.Clear();
});
}
@ -126,14 +121,6 @@ public class AdminStealth : BasePlugin
return player == null ? HookResult.Continue : HookResult.Stop;
}
private HookResult OnAnyCommand(CCSPlayerController? player, CommandInfo command)
{
if (player is not { IsValid: true, IsBot: false } ||
!_hideAliases.Contains(command.GetArg(0), StringComparer.OrdinalIgnoreCase) || CanHide(player))
return HookResult.Continue;
return HookResult.Stop;
}
private HookResult OnHideTyped(CCSPlayerController? player, CommandInfo command)
{
if (player is { IsValid: true } && !_restoring.Contains(player.Slot))
@ -148,42 +135,20 @@ public class AdminStealth : BasePlugin
player!.PrintToChat($" You're hidden, so you can't join a team. Type {ChatAlias()} to show yourself.");
CloseTeamScreen(player);
return HookResult.Stop;
}
// M opens the team screen on the client, where the server can't block it, and a player with no
// team (as hidden admins are) can't close it without picking. Telling only their client they
// joined spectators doesn't close it (tried live), so do what css_hide does: really move them
// to spectators and back to no team, with the team screen disabled around it. Their team
// events aren't broadcast while hidden (OnPlayerTeam), and our SimpleAdmin fork doesn't unhide
// for spectators.
private readonly HashSet<int> _closingTeamScreen = [];
private void CloseTeamScreen(CCSPlayerController player)
{
// M opens the team screen on the client, where the server can't block it, and a player with
// no team (as hidden admins are) can't close it without picking. Answer the pick by telling
// only their client they joined spectators, with the team screen disabled around it, so
// nobody else ever sees them on a team.
var slot = player.Slot;
if (!_closingTeamScreen.Add(slot))
return;
Server.ExecuteCommand("sv_disable_teamselect_menu 1");
Server.NextFrame(() =>
AddTimer(0.3f, () =>
{
var p = Utilities.GetPlayerFromSlot(slot);
if (p is { IsValid: true } && IsHidden(p) && p.TeamNum == (byte)CsTeam.None)
p.ChangeTeam(CsTeam.Spectator);
});
AddTimer(0.15f, () =>
{
var p = Utilities.GetPlayerFromSlot(slot);
if (p is { IsValid: true } && IsHidden(p) && p.TeamNum == (byte)CsTeam.Spectator)
p.ChangeTeam(CsTeam.None);
}, TimerFlags.STOP_ON_MAPCHANGE);
AddTimer(0.5f, () =>
{
_closingTeamScreen.Remove(slot);
Server.ExecuteCommand("sv_disable_teamselect_menu 0");
if (p is { IsValid: true } && IsHidden(p))
SendTeamEvent(p, (int)CsTeam.Spectator, p.TeamNum);
}, TimerFlags.STOP_ON_MAPCHANGE);
AddTimer(0.6f, () => Server.ExecuteCommand("sv_disable_teamselect_menu 0"), TimerFlags.STOP_ON_MAPCHANGE);
return HookResult.Stop;
}
private HookResult OnTeamMenu(CCSPlayerController? player, CommandInfo command)
@ -345,7 +310,7 @@ public class AdminStealth : BasePlugin
return;
}
if (CanHide(player))
if (AdminManager.PlayerHasPermissions(player, HidePermission))
{
_restoring.Add(slot);
player.ExecuteClientCommandFromServer(_hideAliases[0]);
@ -353,23 +318,6 @@ public class AdminStealth : BasePlugin
AddTimer(RestoreDelay, () => TryRestore(slot, steamId, attempt + 1), TimerFlags.STOP_ON_MAPCHANGE);
}
// Whether the player may use css_hide: an admin_overrides.json override of it wins, checked the way
// CSS checks the typed command ("all" needs every flag, anything else one of them).
private bool CanHide(CCSPlayerController player)
{
var command = _hideAliases[0];
var data = AdminManager.GetCommandOverrideData(command);
if (data == null || !data.Enabled)
return AdminManager.PlayerHasPermissions(player, HidePermission);
var flags = data.Flags.ToArray();
BaseRequiresPermissions check = data.CheckType == "all"
? new RequiresPermissions(flags)
: new RequiresPermissionsOr(flags);
check.Command = command;
return check.CanExecuteCommand(player);
}
// For an admin SimpleAdmin still counts as hidden who ended up on a team anyway.
private static void MoveOffTeams(CCSPlayerController player)
{

View file

@ -1,7 +1,6 @@
# AdminStealth
A companion to CS2-SimpleAdmin's `css_hide` (`!hide`, `@css/kick`, or whatever an
`admin_overrides.json` entry for `css_hide` says: reconnect re-hiding follows it too). SimpleAdmin's hide moves the
A companion to CS2-SimpleAdmin's `css_hide` (`!hide`, `@css/kick`). SimpleAdmin's hide moves the
admin off the scoreboard. This plugin makes it look like they left the server:
- When an admin hides, every other player gets a `player_disconnect` event for them, sent to each
@ -20,12 +19,9 @@ admin off the scoreboard. This plugin makes it look like they left the server:
the team select screen doesn't open, until the restore hides them again.
- The team select screen: the server's "team" menu message (`CS_UM_VGUIMenu`) is never sent to
a hidden admin. M opens it on the client regardless, and a player with no team can't close it
without picking, so a pick moves them to spectators and straight back to no team, as `css_hide`
does (with `sv_disable_teamselect_menu` on around it), which closes it. The team events aren't
broadcast; for about 0.15 s they're on the spectator list of anyone looking at the scoreboard.
without picking, so a pick is answered by telling only that client it joined spectators
(with `sv_disable_teamselect_menu` on around it); nobody else sees a team change.
- Restoring runs `css_hide` at most once: it toggles, so a second run could unhide them.
- Players who may not use `css_hide` (or its aliases) get no reply at all when they try it, from
console or chat: not even CSS's missing-permission message.
Needs our SimpleAdmin fork (1.8.2b-zio2 or later), where moving to spectators doesn't unhide a
hidden admin. Never hot-reload CS2-SimpleAdmin while this runs: CSS keeps handing out the old

View file

@ -19,13 +19,8 @@ set -euo pipefail
FORGEJO_URL="https://git.zio.sh"
OWNER="cs2"
REPO="AdminStealth"
# Needs write:repository. Only uploading needs it; downloading from the public repo doesn't. From the
# environment, or else from .forgejo-token in the cs2/ folder above every plugin (outside their
# repos, mode 600). Don't commit a real token.
TOKEN_FILE="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/../../.forgejo-token"
if [[ -z "${FORGEJO_TOKEN:-}" && -r "$TOKEN_FILE" ]]; then
FORGEJO_TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
fi
# Placeholder - replace, or set FORGEJO_TOKEN in the environment instead. Needs write:repository.
# Only uploading needs it; downloading from the public repo doesn't. Don't commit a real token.
FORGEJO_TOKEN="${FORGEJO_TOKEN:-CHANGE_ME_FORGEJO_TOKEN}"
# Only these ship from the publish output. CounterStrikeSharp.API.dll is excluded by the csproj
@ -42,7 +37,7 @@ if [[ -z "$TAG" ]]; then
exit 1
fi
if [[ "$FORGEJO_TOKEN" == "CHANGE_ME_FORGEJO_TOKEN" ]]; then
echo "Set FORGEJO_TOKEN (export it, or put it in $TOKEN_FILE) first." >&2
echo "Set FORGEJO_TOKEN (edit release.sh or export it) first." >&2
exit 1
fi
for tool in podman jq curl git; do