diff --git a/Server/BanLists.cs b/Server/BanLists.cs index a21f0c9..dbe0d12 100644 --- a/Server/BanLists.cs +++ b/Server/BanLists.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Sockets; using System.Text; +using Shared; using Shared.Packet.Packets; namespace Server; @@ -30,6 +31,12 @@ public static class BanLists { } } + private static ISet Stages { + get { + return Settings.Instance.BanList.Stages; + } + } + private static bool IsIPv4(string str) { return IPAddress.TryParse(str, out IPAddress? ip) @@ -62,6 +69,10 @@ public static class BanLists { return Profiles.Contains(id); } + public static bool IsStageBanned(string stage) { + return Stages.Contains(stage); + } + public static bool IsClientBanned(Client user) { return IsProfileBanned(user) || IsIPv4Banned(user); } @@ -91,6 +102,10 @@ public static class BanLists { Profiles.Add(id); } + private static void BanStage(string stage) { + Stages.Add(stage); + } + private static void BanClient(Client user) { BanProfile(user); BanIPv4(user); @@ -121,22 +136,36 @@ public static class BanLists { Profiles.Remove(id); } + private static void UnbanStage(string stage) { + Stages.Remove(stage); + } + private static void Save() { Settings.SaveSettings(true); } - public static void Crash(Client user, bool permanent = false) { + public static void Crash( + Client user, + bool permanent = false, + bool dispose_user = true, + int delay_ms = 0 + ) { user.Ignored = true; Task.Run(async () => { + if (delay_ms > 0) { + await Task.Delay(delay_ms); + } await user.Send(new ChangeStagePacket { Id = (permanent ? "$agogus/ban4lyfe" : "$among$us/cr4sh%"), Stage = (permanent ? "$ejected" : "$agogusStage"), Scenario = (sbyte) (permanent ? 69 : 21), SubScenarioType = (byte) (permanent ? 21 : 69), }); - user.Dispose(); + if (dispose_user) { + user.Dispose(); + } }); } @@ -149,7 +178,7 @@ public static class BanLists { public static string HandleBanCommand(string[] args, MUCH much) { if (args.Length == 0) { - return "Usage: ban {list|enable|disable|player|profile|ip} ..."; + return "Usage: ban {list|enable|disable|player|profile|ip|stage} ..."; } string cmd = args[0]; @@ -157,7 +186,7 @@ public static class BanLists { switch (cmd) { default: - return "Usage: ban {list|enable|disable|player|profile|ip} ..."; + return "Usage: ban {list|enable|disable|player|profile|ip|stage} ..."; case "list": if (args.Length != 0) { @@ -176,6 +205,11 @@ public static class BanLists { list.Append(string.Join("\n- ", Profiles)); } + if (Stages.Count > 0) { + list.Append("\nBanned stages:\n- "); + list.Append(string.Join("\n- ", Stages)); + } + return list.ToString(); case "enable": @@ -247,13 +281,35 @@ public static class BanLists { CrashMultiple(args, much); Save(); return "Banned ip: " + args[0]; + + case "stage": + if (args.Length != 1) { + return "Usage: ban stage "; + } + string? stage = Shared.Stages.Input2Stage(args[0]); + if (stage == null) { + return "Invalid stage name!"; + } + if (IsStageBanned(stage)) { + return "Stage " + stage + " is already banned."; + } + var stages = Shared.Stages + .StagesByInput(args[0]) + .Where(s => !IsStageBanned(s)) + .ToList() + ; + foreach (string s in stages) { + BanStage(s); + } + Save(); + return "Banned stage: " + string.Join(", ", stages); } } public static string HandleUnbanCommand(string[] args) { if (args.Length != 2) { - return "Usage: unban {profile|ip} "; + return "Usage: unban {profile|ip|stage} "; } string cmd = args[0]; @@ -261,7 +317,7 @@ public static class BanLists { switch (cmd) { default: - return "Usage: unban {profile|ip} "; + return "Usage: unban {profile|ip|stage} "; case "profile": if (!Guid.TryParse(val, out Guid id)) { @@ -284,6 +340,22 @@ public static class BanLists { UnbanIPv4(val); Save(); return "Unbanned ip: " + val; + + case "stage": + string stage = Shared.Stages.Input2Stage(val) ?? val; + if (!IsStageBanned(stage)) { + return "Stage " + stage + " is not banned."; + } + var stages = Shared.Stages + .StagesByInput(val) + .Where(IsStageBanned) + .ToList() + ; + foreach (string s in stages) { + UnbanStage(s); + } + Save(); + return "Unbanned stage: " + string.Join(", ", stages); } } } diff --git a/Server/Program.cs b/Server/Program.cs index 8bb3c7d..6bd225a 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -113,6 +113,11 @@ float MarioSize(bool is2d) => is2d ? 180 : 160; server.PacketHandler = (c, p) => { switch (p) { case GamePacket gamePacket: { + if (BanLists.Enabled && BanLists.IsStageBanned(gamePacket.Stage)) { + c.Logger.Warn($"Crashing player for entering banned stage {gamePacket.Stage}."); + BanLists.Crash(c, false, false, 500); + return false; + } c.Logger.Info($"Got game packet {gamePacket.Stage}->{gamePacket.ScenarioNum}"); c.Metadata["scenario"] = gamePacket.ScenarioNum; c.Metadata["2d"] = gamePacket.Is2d; diff --git a/Server/Settings.cs b/Server/Settings.cs index d1610f3..22a7dc9 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -62,6 +62,7 @@ public class Settings { public bool Enabled { get; set; } = false; public ISet Players { get; set; } = new SortedSet(); public ISet IpAddresses { get; set; } = new SortedSet(); + public ISet Stages { get; set; } = new SortedSet(); } public class FlipTable { diff --git a/Shared/Stages.cs b/Shared/Stages.cs index bbb8171..f4e00ab 100644 --- a/Shared/Stages.cs +++ b/Shared/Stages.cs @@ -11,7 +11,7 @@ public static class Stages { return mapName; } // exact stage value - if (Stage2Alias.ContainsKey(input)) { + if (IsStage(input)) { return input; } // force input value with a ! @@ -29,6 +29,32 @@ public static class Stages { return result; } + public static bool IsAlias(string input) { + return Alias2Stage.ContainsKey(input); + } + + public static bool IsStage(string input) { + return Stage2Alias.ContainsKey(input); + } + + public static IEnumerable StagesByInput(string input) { + if (IsAlias(input)) { + var stages = Stage2Alias + .Where(e => e.Value == input) + .Select(e => e.Key) + ; + foreach (string stage in stages) { + yield return stage; + } + } + else { + string? stage = Input2Stage(input); + if (stage != null) { + yield return stage; + } + } + } + public static readonly Dictionary Alias2Stage = new Dictionary() { { "cap", "CapWorldHomeStage" }, { "cascade", "WaterfallWorldHomeStage" },