From 4f8bd7275bf3ad5914b5ac82caa5aeef3514f6c3 Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:15:18 +0300 Subject: [PATCH 1/2] feat: multigroup system --- EXILED/Exiled.Events/Commands/TpsCommand.cs | 11 +- EXILED/Exiled.Permissions/Config.cs | 6 + .../Extensions/Permissions.cs | 47 ++++++-- .../Features/MultipleGroups/BaseCheckPatch.cs | 81 ++++++++++++++ .../Features/MultipleGroups/MultiGroup.cs | 105 ++++++++++++++++++ EXILED/Exiled.Permissions/Permissions.cs | 5 + 6 files changed, 240 insertions(+), 15 deletions(-) create mode 100644 EXILED/Exiled.Permissions/Features/MultipleGroups/BaseCheckPatch.cs create mode 100644 EXILED/Exiled.Permissions/Features/MultipleGroups/MultiGroup.cs diff --git a/EXILED/Exiled.Events/Commands/TpsCommand.cs b/EXILED/Exiled.Events/Commands/TpsCommand.cs index 14c0def05e..d23d2a3f88 100644 --- a/EXILED/Exiled.Events/Commands/TpsCommand.cs +++ b/EXILED/Exiled.Events/Commands/TpsCommand.cs @@ -30,15 +30,12 @@ public class TpsCommand : ICommand /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) { - double diff = Server.SmoothTps / Server.MaxTps; - string color = diff switch + foreach (string data in ServerStatic.PermissionsHandler._config.GetStringList("AdditionalGroups")) { - > 0.9 => "green", - > 0.5 => "yellow", - _ => "red" - }; + Log.Info(data); + } - response = $"{Server.SmoothTps}/{Server.MaxTps}"; + response = "uwu"; return true; } } diff --git a/EXILED/Exiled.Permissions/Config.cs b/EXILED/Exiled.Permissions/Config.cs index b0aabc52f0..81cf8fb394 100644 --- a/EXILED/Exiled.Permissions/Config.cs +++ b/EXILED/Exiled.Permissions/Config.cs @@ -44,6 +44,12 @@ public Config() [Description("The permissions full path")] public string FullPath { get; private set; } + /// + /// Gets a value indicating whether multi-group system should be used. + /// + [Description("Whether MultiGroup system should be used")] + public bool UseMultiGroup { get; private set; } = false; + /// public bool IsEnabled { get; set; } = true; diff --git a/EXILED/Exiled.Permissions/Extensions/Permissions.cs b/EXILED/Exiled.Permissions/Extensions/Permissions.cs index 56823e3feb..8127cca42e 100644 --- a/EXILED/Exiled.Permissions/Extensions/Permissions.cs +++ b/EXILED/Exiled.Permissions/Extensions/Permissions.cs @@ -14,16 +14,14 @@ namespace Exiled.Permissions.Extensions using System.Text; using CommandSystem; - using Exiled.API.Extensions; using Exiled.API.Features; using Exiled.API.Features.Pools; using Features; - + using Features.MultipleGroups; using Properties; using Query; using RemoteAdmin; - using YamlDotNet.Core; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -207,15 +205,48 @@ public static bool CheckPermission(this Player player, string permission) Log.Debug($"UserID: {player.UserId} | PlayerId: {player.Id}"); Log.Debug($"Permission string: {permission}"); - string plyGroupKey = player.Group is not null ? ServerStatic.PermissionsHandler.Groups.FirstOrDefault(g => g.Value.EqualsTo(player.Group)).Key : null; - Log.Debug($"GroupKey: {plyGroupKey ?? "(null)"}"); + if (!MultiGroup.IsActive) + { + string plyGroupKey = player.Group is not null ? ServerStatic.PermissionsHandler.Groups.FirstOrDefault(g => g.Value.EqualsTo(player.Group)).Key : null; + Log.Debug($"GroupKey: {plyGroupKey ?? "(null)"}"); + + if (plyGroupKey is null || !Groups.TryGetValue(plyGroupKey, out Group group)) + { + Log.Debug("The source group is null, the default group is used"); + group = DefaultGroup; + } + + return group.CheckPermission(permission); + } + + if (!MultiGroup.Permissions.TryGetValue(player.UserId, out string[] data)) + return false; - if (plyGroupKey is null || !Groups.TryGetValue(plyGroupKey, out Group group)) + foreach (string group in data) { - Log.Debug("The source group is null, the default group is used"); - group = DefaultGroup; + if (!Groups.TryGetValue(group, out Group value)) + { + Log.Error($"Invalid group name: {group}"); + continue; + } + + if (value.CheckPermission(permission)) + return true; } + return false; + } + + /// + /// Checks a group's permission. + /// + /// The group to be checked. + /// The permission to be checked. + /// if the player's current or native group has permissions; otherwise, . + public static bool CheckPermission(this Group group, string permission) + { + group ??= DefaultGroup; + if (group is null) { Log.Debug("There's no default group, returning false..."); diff --git a/EXILED/Exiled.Permissions/Features/MultipleGroups/BaseCheckPatch.cs b/EXILED/Exiled.Permissions/Features/MultipleGroups/BaseCheckPatch.cs new file mode 100644 index 0000000000..26520cc305 --- /dev/null +++ b/EXILED/Exiled.Permissions/Features/MultipleGroups/BaseCheckPatch.cs @@ -0,0 +1,81 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Permissions.Features.MultipleGroups +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using CommandSystem; + using Exiled.API.Features; + using HarmonyLib; + using Query; + using RemoteAdmin; + + /// + /// Patches base-game method to implement multiple groups check per user. + /// + [HarmonyPatch] + internal static class BaseCheckPatch + { + private static bool Check(ICommandSender sender, PlayerPermissions[] permissions) + { + if (sender is not CommandSender commandSender) + return false; + + if (commandSender.FullPermissions || sender is ServerConsoleSender || commandSender == Server.Host.Sender) + { + return true; + } + + if (sender is PlayerCommandSender || sender is QueryCommandSender) + { + if (!Player.TryGet(sender, out Player player)) + return false; + + if (!MultiGroup.Permissions.TryGetValue(player.UserId, out string[] data)) + return false; + + foreach (string groupName in data) + { + UserGroup group = ServerStatic.PermissionsHandler.GetGroup(groupName); + if (group == null) + { + Log.Error($"Invalid group name: {groupName}"); + continue; + } + + if (PermissionsHandler.IsPermitted(group.Permissions, permissions)) + return true; + } + } + + return false; + } + + private static bool Check(ICommandSender sender, PlayerPermissions permissions) => Check(sender, new[] { permissions }); + + [HarmonyPatch(typeof(Misc), nameof(Misc.CheckPermission), typeof(ICommandSender), typeof(PlayerPermissions))] + private static IEnumerable Transpiler(IEnumerable instructions) + { + yield return new(OpCodes.Ldarg_0); + yield return new(OpCodes.Ldarg_1); + yield return new(OpCodes.Call, AccessTools.Method(typeof(BaseCheckPatch), nameof(Check), new[] { typeof(ICommandSender), typeof(PlayerPermissions) })); + yield return new(OpCodes.Ret); + } + + [HarmonyTranspiler] + [HarmonyPatch(typeof(Misc), nameof(Misc.CheckPermission), typeof(ICommandSender), typeof(PlayerPermissions[]))] + private static IEnumerable TranspilerMore(IEnumerable instructions) + { + yield return new(OpCodes.Ldarg_0); + yield return new(OpCodes.Ldarg_1); + yield return new(OpCodes.Call, AccessTools.Method(typeof(BaseCheckPatch), nameof(Check), new[] { typeof(ICommandSender), typeof(PlayerPermissions[]) })); + yield return new(OpCodes.Ret); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Permissions/Features/MultipleGroups/MultiGroup.cs b/EXILED/Exiled.Permissions/Features/MultipleGroups/MultiGroup.cs new file mode 100644 index 0000000000..01601b6d5d --- /dev/null +++ b/EXILED/Exiled.Permissions/Features/MultipleGroups/MultiGroup.cs @@ -0,0 +1,105 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Permissions.Features.MultipleGroups +{ +#pragma warning disable SA1401 + using System; + using System.Collections.Generic; + + using Exiled.API.Features; + using Exiled.API.Features.Pools; + + using HarmonyLib; + + /// + /// Main class for Multi-Group system. + /// + public static class MultiGroup + { + /// + /// A value indicating whether the system is active. + /// + internal static bool IsActive = false; + + /// + /// Internal harmony instance. + /// + private static Harmony harmony; + + /// + /// Gets a collection of all groups of all users. + /// + public static Dictionary Permissions { get; private set; } + + /// + /// Main method for initializing MultiGroup. + /// + /// Whether the load should be forced, ignoring config. + public static void Init(bool force = false) // TODO: add list pooling + { + if (!Exiled.Permissions.Permissions.Instance.Config.UseMultiGroup && !force) + return; + + List groups = ServerStatic.PermissionsHandler._config.GetStringList("AdditionalGroups"); + Dictionary> users = new(); + + foreach (string entry in groups) + { + if (!entry.Contains(": ")) + { + Log.Error($"Invalid entry at AdditionalGroups in config_remoteadmin.txt: {entry}"); + continue; + } + + int length = entry.IndexOf(": ", StringComparison.Ordinal); + string key = entry.Substring(0, length); + string value = entry.Substring(length + 2); + + users.GetOrAdd(key, () => ListPool.Pool.Get()).Add(value); + } + + if (users.Count == 0) + return; + + try + { + harmony = new("exiled.permissions-multigroups"); + harmony.CreateClassProcessor(typeof(BaseCheckPatch)).Patch(); + } + catch (HarmonyException exception) + { + Log.Error($"Patch error while patching for MultiGroups! {exception}"); + return; + } + + IsActive = true; + Permissions = new(); + + foreach (KeyValuePair> kvp in users) + { + if (ServerStatic.PermissionsHandler.Members.TryGetValue(kvp.Key, out string group)) + kvp.Value.Add(group); + + Permissions[kvp.Key] = ListPool.Pool.ToArrayReturn(kvp.Value); + } + } + + /// + /// Disables module and frees all resources. + /// + public static void Disable() + { + if (!IsActive) + return; + + IsActive = false; + harmony.UnpatchAll("exiled.permissions-multigroups"); + Permissions.Clear(); + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Permissions/Permissions.cs b/EXILED/Exiled.Permissions/Permissions.cs index eceb43810d..a279ac148e 100644 --- a/EXILED/Exiled.Permissions/Permissions.cs +++ b/EXILED/Exiled.Permissions/Permissions.cs @@ -8,6 +8,7 @@ namespace Exiled.Permissions { using Exiled.API.Features; + using Features.MultipleGroups; using MEC; @@ -36,12 +37,16 @@ public override void OnEnabled() { Extensions.Permissions.Create(); Extensions.Permissions.Reload(); + + MultiGroup.Init(); }); } /// public override void OnDisabled() { + MultiGroup.Disable(); + base.OnDisabled(); Extensions.Permissions.Groups.Clear(); From 3396f2afe510773dad465164e973ed2538ddcc76 Mon Sep 17 00:00:00 2001 From: VALERA771 <72030575+VALERA771@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:23:11 +0300 Subject: [PATCH 2/2] revert: tps command chance --- EXILED/Exiled.Events/Commands/TpsCommand.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.Events/Commands/TpsCommand.cs b/EXILED/Exiled.Events/Commands/TpsCommand.cs index d23d2a3f88..14c0def05e 100644 --- a/EXILED/Exiled.Events/Commands/TpsCommand.cs +++ b/EXILED/Exiled.Events/Commands/TpsCommand.cs @@ -30,12 +30,15 @@ public class TpsCommand : ICommand /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) { - foreach (string data in ServerStatic.PermissionsHandler._config.GetStringList("AdditionalGroups")) + double diff = Server.SmoothTps / Server.MaxTps; + string color = diff switch { - Log.Info(data); - } + > 0.9 => "green", + > 0.5 => "yellow", + _ => "red" + }; - response = "uwu"; + response = $"{Server.SmoothTps}/{Server.MaxTps}"; return true; } }