The current BepInExPack is broken due to the Oakveil update, and mods installed through a mod manager may not work. Join the modding Discord for more information.
Decompiled source of VampireCommandFramework v0.9.0
VampireCommandFramework.dll
Decompiled a year agousing System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using HarmonyLib; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using ProjectM; using ProjectM.Network; using SemanticVersioning; using Unity.Collections; using Unity.Entities; using UnityEngine; using VCF.Core.Basics; using VampireCommandFramework; using VampireCommandFramework.Basics; using VampireCommandFramework.Breadstone; using VampireCommandFramework.Common; using VampireCommandFramework.Registry; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("deca")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Framework for commands in V Rising")] [assembly: AssemblyFileVersion("0.9.0.0")] [assembly: AssemblyInformationalVersion("0.9.0+24.Branch.main.Sha.d432c15d766e8a2dc2260b16d84a5c4dc4882c7e")] [assembly: AssemblyProduct("VampireCommandFramework")] [assembly: AssemblyTitle("VampireCommandFramework")] [assembly: InternalsVisibleTo("VCF.Tests")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.9.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace VampireCommandFramework { public class ChatCommandContext : ICommandContext { private static int maxMessageLength = 509; public VChatEvent Event { get; } public User User => Event.User; public IServiceProvider Services { get; } public string Name { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) User user = User; return ((object)(FixedString64Bytes)(ref user.CharacterName)).ToString(); } } public bool IsAdmin => User.IsAdmin; public ChatCommandContext(VChatEvent e) { Event = e; } public void Reply(string v) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (v.Length > maxMessageLength) { v = v.Substring(0, maxMessageLength); } ServerChatUtils.SendSystemMessageToClient(VWorld.Server.EntityManager, User, v); } public CommandException Error(string LogMessage) { return new CommandException(LogMessage); } } public static class Color { public static string Red = "red"; public static string Primary = "#b0b"; public static string White = "#eee"; public static string LightGrey = "#ccc"; public static string Yellow = "#dd0"; public static string DarkGreen = "#0c0"; } public abstract class CommandArgumentConverter<T> : CommandArgumentConverter<T, ICommandContext> { } public abstract class CommandArgumentConverter<T, C> where C : ICommandContext { public abstract T Parse(C ctx, string input); } [AttributeUsage(AttributeTargets.Method)] public sealed class CommandAttribute : Attribute { public string Name { get; } public string ShortHand { get; } public string Usage { get; } public string Description { get; } public string Id { get; } public bool AdminOnly { get; } public CommandAttribute(string name, string shortHand = null, string usage = null, string description = null, string id = null, bool adminOnly = false) { Name = name; ShortHand = shortHand; Usage = usage; Description = description; Id = id ?? Name.Replace(" ", "-"); AdminOnly = adminOnly; } } [Serializable] public class CommandException : Exception { public CommandException() { } public CommandException(string message) : base(message) { } public CommandException(string message, Exception innerException) : base(message, innerException) { } protected CommandException(SerializationInfo info, StreamingContext context) : base(info, context) { } } [AttributeUsage(AttributeTargets.Class)] public sealed class CommandGroupAttribute : Attribute { public string Name { get; } public string ShortHand { get; } public CommandGroupAttribute(string name, string shortHand = null) { Name = name; ShortHand = shortHand; } } public abstract class CommandMiddleware { public virtual bool CanExecute(ICommandContext ctx, CommandAttribute attribute, MethodInfo method) { return true; } public virtual void BeforeExecute(ICommandContext ctx, CommandAttribute attribute, MethodInfo method) { } public virtual void AfterExecute(ICommandContext ctx, CommandAttribute attribute, MethodInfo method) { } } public enum CommandResult { Unmatched, UsageError, CommandError, InternalError, Denied, Success } public static class Format { public enum FormatMode { GameChat, None } public static FormatMode Mode { get; set; } public static string B(string input) { return input.Bold(); } public static string Bold(this string input) { return (Mode == FormatMode.GameChat) ? ("<b>" + input + "</b>") : input; } public static string I(string input) { return input.Italic(); } public static string Italic(this string input) { return (Mode == FormatMode.GameChat) ? ("<i>" + input + "</i>") : input; } public static string Underline(this string input) { return (Mode == FormatMode.GameChat) ? ("<u>" + input + "</u>") : input; } public static string Color(this string input, string color) { return (Mode == FormatMode.GameChat) ? $"<color={color}>{input}</color>" : input; } public static string Size(this string input, int size) { return (Mode == FormatMode.GameChat) ? $"<size={size}>{input}</size>" : input; } public static string Small(this string input) { return input.Size(10); } public static string Normal(this string input) { return input.Size(16); } public static string Medium(this string input) { return input.Size(20); } public static string Large(this string input) { return input.Size(24); } } public interface ICommandContext { IServiceProvider Services { get; } string Name { get; } bool IsAdmin { get; } CommandException Error(string LogMessage); void Reply(string v); } public interface IConverterUsage { string Usage { get; } } [BepInPlugin("gg.deca.VampireCommandFramework", "VampireCommandFramework", "0.9.0")] internal class Plugin : BasePlugin { private Harmony _harmony; public override void Load() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown Log.Instance = ((BasePlugin)this).Log; if (!VWorld.IsServer) { ((BasePlugin)this).Log.LogMessage((object)"Note: Vampire Command Framework is loading on the client but only adds functionality on the server at this time, seeing this message is not a problem or bug."); return; } _harmony = new Harmony("gg.deca.VampireCommandFramework"); _harmony.PatchAll(); CommandRegistry.RegisterCommandType(typeof(HelpCommands)); CommandRegistry.RegisterCommandType(typeof(BepInExConfigCommands)); ((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.TryGetValue("gg.deca.VampireCommandFramework", out var value); ManualLogSource log = ((BasePlugin)this).Log; bool flag = default(bool); BepInExMessageLogInterpolatedStringHandler val = new BepInExMessageLogInterpolatedStringHandler(12, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("VCF Loaded: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Version>((value != null) ? value.Metadata.Version : null); } log.LogMessage(val); } public override bool Unload() { _harmony.UnpatchSelf(); return true; } } public static class CommandRegistry { internal const string DEFAULT_PREFIX = "."; private static CommandCache _cache = new CommandCache(); internal static Dictionary<Type, (object instance, MethodInfo tryParse, Type contextType)> _converters = new Dictionary<Type, (object, MethodInfo, Type)>(); private static List<CommandMiddleware> DEFAULT_MIDDLEWARES = new List<CommandMiddleware> { new BasicAdminCheck() }; public static List<CommandMiddleware> Middlewares { get; } = new List<CommandMiddleware> { new BasicAdminCheck() }; internal static Dictionary<Assembly, Dictionary<CommandMetadata, List<string>>> AssemblyCommandMap { get; } = new Dictionary<Assembly, Dictionary<CommandMetadata, List<string>>>(); internal static void Reset() { Middlewares.Clear(); Middlewares.AddRange(DEFAULT_MIDDLEWARES); AssemblyCommandMap.Clear(); _converters.Clear(); _cache = new CommandCache(); } internal static bool CanCommandExecute(ICommandContext ctx, CommandMetadata command) { foreach (CommandMiddleware middleware in Middlewares) { try { if (!middleware.CanExecute(ctx, command.Attribute, command.Method)) { return false; } } catch (Exception value) { Log.Error($"Error executing {middleware.GetType().Name} {value}"); return false; } } return true; } public static CommandResult Handle(ICommandContext ctx, string input) { CacheResult command2 = _cache.GetCommand(input); CommandMetadata command3 = command2.Command; string[] args = command2.Args; CommandMetadata commandMetadata = command3; string[] array = args; if (!command2.IsMatched) { if (!command2.HasPartial) { return CommandResult.Unmatched; } foreach (CommandMetadata partialMatch in command2.PartialMatches) { ctx.SysReply(HelpCommands.PrintShortHelp(partialMatch)); } return CommandResult.UsageError; } if (!commandMetadata.ContextType.IsAssignableFrom(ctx?.GetType())) { Log.Warning($"Matched [{commandMetadata.Attribute.Id}] but can not assign {commandMetadata.ContextType.Name} from {ctx?.GetType().Name}"); return CommandResult.InternalError; } if (commandMetadata.Constructor != null && !commandMetadata.ConstructorType.IsAssignableFrom(ctx?.GetType())) { Log.Warning($"Matched [{commandMetadata.Attribute.Id}] but can not assign {commandMetadata.ConstructorType.Name} from {ctx?.GetType().Name}"); ctx.InternalError(); return CommandResult.InternalError; } int num = array.Length; int num2 = commandMetadata.Parameters.Length; object[] array2 = new object[num2 + 1]; array2[0] = ctx; if (num != num2) { if (!commandMetadata.Parameters.Skip(num).All((ParameterInfo p) => p.HasDefaultValue)) { return CommandResult.UsageError; } for (int i = num; i < num2; i++) { array2[i + 1] = commandMetadata.Parameters[i].DefaultValue; } } for (int j = 0; j < num; j++) { ParameterInfo parameterInfo = commandMetadata.Parameters[j]; string text = array[j]; if (_converters.TryGetValue(parameterInfo.ParameterType, out (object, MethodInfo, Type) value)) { var (obj, methodInfo, type) = value; if (!type.IsAssignableFrom(ctx.GetType())) { Log.Error("Converter type " + type.Name + " is not assignable from " + ctx.GetType().Name); ctx.InternalError(); return CommandResult.InternalError; } object[] parameters = new object[2] { ctx, text }; try { object obj2 = methodInfo.Invoke(obj, parameters); array2[j + 1] = obj2; } catch (TargetInvocationException ex) { if (ex.InnerException is CommandException ex2) { ctx.Reply("<color=red>[error]</color> Failed converted parameter: " + ex2.Message); return CommandResult.UsageError; } Log.Warning($"Hit unexpected exception {ex}"); ctx.InternalError(); return CommandResult.InternalError; } catch (Exception value2) { Log.Warning($"Hit unexpected exception {value2}"); ctx.InternalError(); return CommandResult.InternalError; } continue; } TypeConverter converter = TypeDescriptor.GetConverter(parameterInfo.ParameterType); try { object obj3 = converter.ConvertFromInvariantString(text); if (converter is EnumConverter && !Enum.IsDefined(parameterInfo.ParameterType, obj3)) { ctx.Reply($"<color=red>[error]</color> Invalid value {obj3} for {parameterInfo.ParameterType.Name}"); return CommandResult.UsageError; } array2[j + 1] = obj3; } catch (Exception ex3) { ctx.Reply("<color=red>[error]</color> Failed converted parameter: " + ex3.Message); return CommandResult.UsageError; } } object obj4 = null; if (!commandMetadata.Method.IsStatic && (!commandMetadata.Method.DeclaringType.IsAbstract || !commandMetadata.Method.DeclaringType.IsSealed)) { try { object? obj5; if (!(commandMetadata.Constructor == null)) { ConstructorInfo constructor = commandMetadata.Constructor; object[] parameters2 = new ICommandContext[1] { ctx }; obj5 = constructor.Invoke(parameters2); } else { obj5 = Activator.CreateInstance(commandMetadata.Method.DeclaringType); } obj4 = obj5; } catch (TargetInvocationException ex4) { if (ex4.InnerException is CommandException ex5) { ctx.SysReply(ex5.Message); } else { ctx.InternalError(); } return CommandResult.InternalError; } } if (!CanCommandExecute(ctx, commandMetadata)) { ctx.Reply("<color=red>[denied]</color> " + commandMetadata.Attribute.Id); return CommandResult.Denied; } HandleBeforeExecute(ctx, commandMetadata); CommandException ex7 = default(CommandException); try { commandMetadata.Method.Invoke(obj4, array2); } catch (TargetInvocationException ex6) when (((Func<bool>)delegate { // Could not convert BlockContainer to single expression ex7 = ex6.InnerException as CommandException; return ex7 != null; }).Invoke()) { ctx.Reply("<color=red>[error]</color> " + ex7.Message); return CommandResult.CommandError; } catch (Exception value3) { Log.Warning($"Hit unexpected exception executing command {commandMetadata.Attribute.Id}\n: {value3}"); ctx.InternalError(); return CommandResult.InternalError; } HandleAfterExecute(ctx, commandMetadata); return CommandResult.Success; static void HandleAfterExecute(ICommandContext ctx, CommandMetadata command) { Middlewares.ForEach(delegate(CommandMiddleware m) { m.AfterExecute(ctx, command.Attribute, command.Method); }); } static void HandleBeforeExecute(ICommandContext ctx, CommandMetadata command) { Middlewares.ForEach(delegate(CommandMiddleware m) { m.BeforeExecute(ctx, command.Attribute, command.Method); }); } } public static void UnregisterConverter(Type converter) { if (IsGenericConverterContext(converter) || IsSpecificConverterContext(converter)) { Type[] genericTypeArguments = converter.BaseType.GenericTypeArguments; Type type = genericTypeArguments.FirstOrDefault(); if (type == null) { Log.Warning("Could not resolve converter type " + converter.Name); } else if (_converters.ContainsKey(type)) { _converters.Remove(type); Log.Info("Unregistered converter " + converter.Name); } else { Log.Warning("Call to UnregisterConverter for a converter that was not registered. Type: " + converter.Name); } } } internal static bool IsGenericConverterContext(Type rootType) { return rootType?.BaseType?.Name == typeof(CommandArgumentConverter<>).Name; } internal static bool IsSpecificConverterContext(Type rootType) { return rootType?.BaseType?.Name == typeof(CommandArgumentConverter<, >).Name; } public static void RegisterConverter(Type converter) { bool flag = IsGenericConverterContext(converter); bool flag2 = IsSpecificConverterContext(converter); if (!flag && !flag2) { return; } Log.Debug($"Trying to process {converter} as specifc={flag2} generic={flag}"); object item = Activator.CreateInstance(converter); MethodInfo method = converter.GetMethod("Parse", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod); if (method == null) { Log.Error("Can't find TryParse that matches"); return; } Type[] genericTypeArguments = converter.BaseType.GenericTypeArguments; Type type = genericTypeArguments.FirstOrDefault(); if (type == null) { Log.Error("Can't determine generic base type to convert from. "); return; } Type item2 = typeof(ICommandContext); if (flag2) { if (genericTypeArguments.Length != 2 || !typeof(ICommandContext).IsAssignableFrom(genericTypeArguments[1])) { Log.Error("Can't determine generic base type to convert from."); return; } item2 = genericTypeArguments[1]; } _converters.Add(type, (item, method, item2)); } public static void RegisterAll() { RegisterAll(Assembly.GetCallingAssembly()); } public static void RegisterAll(Assembly assembly) { Type[] types = assembly.GetTypes(); Type[] array = types; foreach (Type converter in array) { RegisterConverter(converter); } Type[] array2 = types; foreach (Type type in array2) { RegisterCommandType(type); } } public static void RegisterCommandType(Type type) { CommandGroupAttribute customAttribute = type.GetCustomAttribute<CommandGroupAttribute>(); Assembly assembly = type.Assembly; if (customAttribute != null) { } MethodInfo[] methods = type.GetMethods(); ConstructorInfo customConstructor = (from c in type.GetConstructors() where c.GetParameters().Length == 1 && typeof(ICommandContext).IsAssignableFrom(c.GetParameters().SingleOrDefault()?.ParameterType) select c).FirstOrDefault(); MethodInfo[] array = methods; foreach (MethodInfo method in array) { RegisterMethod(assembly, customAttribute, customConstructor, method); } } private static void RegisterMethod(Assembly assembly, CommandGroupAttribute groupAttr, ConstructorInfo customConstructor, MethodInfo method) { CommandAttribute customAttribute = method.GetCustomAttribute<CommandAttribute>(); if (customAttribute == null) { return; } ParameterInfo[] parameters = method.GetParameters(); ParameterInfo parameterInfo = parameters.FirstOrDefault(); if (parameterInfo == null || parameterInfo.ParameterType is ICommandContext) { Log.Error("Method " + method.Name + " has no CommandContext as first argument"); return; } ParameterInfo[] array = parameters.Skip(1).ToArray(); if (!array.All(delegate(ParameterInfo param) { if (_converters.ContainsKey(param.ParameterType)) { Log.Debug($"Method {method.Name} has a parameter of type {param.ParameterType.Name} which is registered as a converter"); return true; } TypeConverter converter = TypeDescriptor.GetConverter(param.ParameterType); if (converter == null || !converter.CanConvertFrom(typeof(string))) { Log.Warning($"Parameter {param.Name} could not be converted, so {method.Name} will be ignored."); return false; } return true; })) { return; } Type constructorType = customConstructor?.GetParameters().Single().ParameterType; CommandMetadata commandMetadata = new CommandMetadata(customAttribute, method, customConstructor, array, parameterInfo.ParameterType, constructorType, groupAttr); string[] array2 = ((groupAttr != null) ? ((groupAttr.ShortHand != null) ? new string[2] { groupAttr.Name + " ", groupAttr.ShortHand + " " } : new string[1] { groupAttr.Name + " " }) : new string[1] { "" }); string[] array3 = ((customAttribute.ShortHand != null) ? new string[2] { customAttribute.Name, customAttribute.ShortHand } : new string[1] { customAttribute.Name }); string text = "."; List<string> list = new List<string>(); string[] array4 = array2; foreach (string text2 in array4) { string[] array5 = array3; foreach (string text3 in array5) { string text4 = text + text2 + text3; _cache.AddCommand(text4, array, commandMetadata); list.Add(text4); } } AssemblyCommandMap.TryGetValue(assembly, out var value); if (value == null) { value = new Dictionary<CommandMetadata, List<string>>(); } value[commandMetadata] = list; AssemblyCommandMap[assembly] = value; } public static void UnregisterAssembly() { UnregisterAssembly(Assembly.GetCallingAssembly()); } public static void UnregisterAssembly(Assembly assembly) { foreach (TypeInfo definedType in assembly.DefinedTypes) { _cache.RemoveCommandsFromType(definedType); UnregisterConverter(definedType); } AssemblyCommandMap.Remove(assembly); } } public static class PluginInfo { public const string PLUGIN_GUID = "gg.deca.VampireCommandFramework"; public const string PLUGIN_NAME = "VampireCommandFramework"; public const string PLUGIN_VERSION = "0.9.0"; } } namespace VampireCommandFramework.Registry { internal record CacheResult(CommandMetadata Command, string[] Args, IEnumerable<CommandMetadata> PartialMatches) { internal bool IsMatched => Command != null; internal bool HasPartial => PartialMatches?.Any() ?? false; [CompilerGenerated] protected virtual bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("Command = "); builder.Append(Command); builder.Append(", Args = "); builder.Append(Args); builder.Append(", PartialMatches = "); builder.Append(PartialMatches); return true; } } internal class CommandCache { private static Dictionary<Type, HashSet<(string, int)>> _commandAssemblyMap = new Dictionary<Type, HashSet<(string, int)>>(); private Dictionary<string, Dictionary<int, CommandMetadata>> _newCache = new Dictionary<string, Dictionary<int, CommandMetadata>>(); internal void AddCommand(string key, ParameterInfo[] parameters, CommandMetadata command) { int num = parameters.Length; int num2 = parameters.Where((ParameterInfo p) => p.HasDefaultValue).Count(); if (!_newCache.ContainsKey(key)) { _newCache.Add(key, new Dictionary<int, CommandMetadata>()); } for (int i = num - num2; i <= num; i++) { _newCache[key] = _newCache.GetValueOrDefault(key, new Dictionary<int, CommandMetadata>()) ?? new Dictionary<int, CommandMetadata>(); if (_newCache[key].ContainsKey(i)) { Log.Warning($"Command {key} has multiple commands with {i} parameters"); } else { _newCache[key][i] = command; Type declaringType = command.Method.DeclaringType; HashSet<(string, int)> value; HashSet<(string, int)> hashSet = (_commandAssemblyMap.TryGetValue(declaringType, out value) ? value : new HashSet<(string, int)>()); hashSet.Add((key, i)); _commandAssemblyMap[declaringType] = hashSet; } } } internal CacheResult GetCommand(string rawInput) { List<CommandMetadata> list = new List<CommandMetadata>(); foreach (var (text2, dictionary2) in _newCache) { if (rawInput.StartsWith(text2) && (rawInput.Length <= text2.Length || rawInput[text2.Length] == ' ')) { string input = rawInput.Substring(text2.Length).Trim(); string[] array = Utility.GetParts(input).ToArray(); if (dictionary2.TryGetValue(array.Length, out var value)) { return new CacheResult(value, array, null); } list.AddRange(dictionary2.Values); } } return new CacheResult(null, null, list.Distinct()); } internal void RemoveCommandsFromType(Type t) { if (!_commandAssemblyMap.TryGetValue(t, out var value)) { return; } foreach (var (key, key2) in value) { if (_newCache.TryGetValue(key, out var value2)) { value2.Remove(key2); } } _commandAssemblyMap.Remove(t); } internal void Clear() { _newCache.Clear(); } internal void Reset() { throw new NotImplementedException(); } } internal record CommandMetadata(CommandAttribute Attribute, MethodInfo Method, ConstructorInfo Constructor, ParameterInfo[] Parameters, Type ContextType, Type ConstructorType, CommandGroupAttribute GroupAttribute); } namespace VampireCommandFramework.Common { internal static class Log { internal static ManualLogSource Instance { get; set; } public static void Warning(string s) { LogOrConsole(s, delegate(string s) { Instance.LogWarning((object)s); }); } public static void Error(string s) { LogOrConsole(s, delegate(string s) { Instance.LogError((object)s); }); } public static void Debug(string s) { LogOrConsole(s, delegate(string s) { Instance.LogDebug((object)s); }); } public static void Info(string s) { LogOrConsole(s, delegate(string s) { Instance.LogInfo((object)s); }); } private static void LogOrConsole(string message, Action<string> instanceLog) { if (Instance == null) { Console.WriteLine(message); } else { instanceLog(message); } } } internal static class Utility { private const int MAX_MESSAGE_SIZE = 460; internal static List<string> GetParts(string input) { List<string> list = new List<string>(); if (string.IsNullOrWhiteSpace(input)) { return list; } bool flag = false; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < input.Length; i++) { char c = input[i]; if (c == '\\' && i + 1 < input.Length) { char c2 = input[i + 1]; if (c2 == '"') { stringBuilder.Append(c2); i++; continue; } } switch (c) { case '"': flag = !flag; continue; case ' ': if (!flag) { if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); stringBuilder.Clear(); } continue; } break; } stringBuilder.Append(c); } if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); } return list; } internal static void InternalError(this ICommandContext ctx) { ctx.SysReply("An internal error has occurred."); } internal static void SysReply(this ICommandContext ctx, string input) { ctx.Reply("[vcf] ".Color(Color.Primary) + input.Color(Color.White)); } internal static void SysPaginatedReply(this ICommandContext ctx, StringBuilder input) { ctx.SysPaginatedReply(input.ToString()); } internal static void SysPaginatedReply(this ICommandContext ctx, string input) { if (input.Length <= 460) { ctx.SysReply(input); return; } string[] array = SplitIntoPages(input); string[] array2 = array; foreach (string text in array2) { string text2 = text.TrimEnd('\n', '\r', ' '); text2 = Environment.NewLine + text2; ctx.SysReply(text2); } } internal static string[] SplitIntoPages(string rawText, int pageSize = 460) { List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); string[] array = rawText.Split(Environment.NewLine); List<string> list2 = new List<string>(); string[] array2 = array; foreach (string text in array2) { if (text.Length > pageSize) { string text2 = text; while (!string.IsNullOrWhiteSpace(text2) && text2.Length > pageSize) { int num = text2.LastIndexOf(' ', pageSize - (int)((double)pageSize * 0.05)); if (num < 0) { num = Math.Min(pageSize - 1, text2.Length); } list2.Add(text2.Substring(0, num)); text2 = text2.Substring(num); } list2.Add(text2); } else { list2.Add(text); } } foreach (string item in list2) { if (stringBuilder.Length + item.Length > pageSize) { list.Add(stringBuilder.ToString()); stringBuilder.Clear(); } stringBuilder.AppendLine(item); } if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); } return list.ToArray(); } } } namespace VampireCommandFramework.Breadstone { [HarmonyPriority(200)] [HarmonyBefore(new string[] { "gg.deca.Bloodstone" })] [HarmonyPatch(typeof(ChatMessageSystem), "OnUpdate")] public static class ChatMessageSystem_Patch { public static bool Prefix(ChatMessageSystem __instance) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) _ = __instance.__query_661171423_0; if (true) { EntityQuery _query_661171423_ = __instance.__query_661171423_0; Enumerator<Entity> enumerator = ((EntityQuery)(ref _query_661171423_)).ToEntityArray(AllocatorHandle.op_Implicit((Allocator)2)).GetEnumerator(); while (enumerator.MoveNext()) { Entity current = enumerator.Current; EntityManager entityManager = ((ComponentSystemBase)__instance).EntityManager; FromCharacter componentData = ((EntityManager)(ref entityManager)).GetComponentData<FromCharacter>(current); entityManager = ((ComponentSystemBase)__instance).EntityManager; User componentData2 = ((EntityManager)(ref entityManager)).GetComponentData<User>(componentData.User); entityManager = ((ComponentSystemBase)__instance).EntityManager; ChatMessageEvent componentData3 = ((EntityManager)(ref entityManager)).GetComponentData<ChatMessageEvent>(current); string text = ((object)(FixedString512Bytes)(ref componentData3.MessageText)).ToString(); VChatEvent e = new VChatEvent(componentData.User, componentData.Character, text, componentData3.MessageType, componentData2); ChatCommandContext ctx = new ChatCommandContext(e); CommandResult commandResult; try { commandResult = CommandRegistry.Handle(ctx, text); } catch (Exception value) { Log.Error($"Error while handling chat message {value}"); continue; } if (commandResult == CommandResult.Success && text.StartsWith(".help-legacy", StringComparison.InvariantCulture)) { componentData3.MessageText = FixedString512Bytes.op_Implicit(text.Replace("-legacy", string.Empty)); entityManager = ((ComponentSystemBase)__instance).EntityManager; ((EntityManager)(ref entityManager)).SetComponentData<ChatMessageEvent>(current, componentData3); return true; } if (commandResult != 0) { entityManager = VWorld.Server.EntityManager; ((EntityManager)(ref entityManager)).DestroyEntity(current); return true; } } } return true; } } public class VChatEvent { public Entity SenderUserEntity { get; } public Entity SenderCharacterEntity { get; } public string Message { get; } public ChatMessageType Type { get; } public bool Cancelled { get; private set; } = false; public User User { get; } internal VChatEvent(Entity userEntity, Entity characterEntity, string message, ChatMessageType type, User user) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) SenderUserEntity = userEntity; SenderCharacterEntity = characterEntity; Message = message; Type = type; User = user; } public void Cancel() { Cancelled = true; } } internal static class VWorld { private static World _serverWorld; public static World Server { get { if (_serverWorld != null) { return _serverWorld; } _serverWorld = GetWorld("Server") ?? throw new Exception("There is no Server world (yet). Did you install a server mod on the client?"); return _serverWorld; } } public static bool IsServer => Application.productName == "VRisingServer"; public static void SendSystemMessage(this User user, string message) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) ServerChatUtils.SendSystemMessageToClient(Server.EntityManager, user, message); } private static World GetWorld(string name) { Enumerator<World> enumerator = World.s_AllWorlds.GetEnumerator(); while (enumerator.MoveNext()) { World current = enumerator.Current; if (current.Name == name) { _serverWorld = current; return current; } } return null; } } } namespace VampireCommandFramework.Basics { [CommandGroup("config", null)] public class BepInExConfigCommands { [Command("dump", null, null, null, null, true)] public void DumpConfig(ICommandContext ctx, string pluginGuid) { var (guid, val2) = (KeyValuePair<string, PluginInfo>)(ref ((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.FirstOrDefault((KeyValuePair<string, PluginInfo> x) => x.Value.Metadata.GUID.Contains(pluginGuid, StringComparison.InvariantCultureIgnoreCase))); if (val2 != null) { object instance = val2.Instance; BasePlugin val3 = (BasePlugin)((instance is BasePlugin) ? instance : null); if (val3 != null) { DumpConfig(ctx, guid, val3); return; } } foreach (KeyValuePair<string, PluginInfo> plugin in ((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins) { ctx.SysReply("Found: " + plugin.Value.Metadata.GUID); } throw ctx.Error("Can not find that plugin"); } [Command("set", null, null, null, null, true)] public void DumpConfig(ICommandContext ctx, string pluginGuid, string section, string key, string value) { var (text2, val2) = (KeyValuePair<string, PluginInfo>)(ref ((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.FirstOrDefault((KeyValuePair<string, PluginInfo> x) => x.Value.Metadata.GUID.Contains(pluginGuid, StringComparison.InvariantCultureIgnoreCase))); if (val2 != null) { object instance = val2.Instance; BasePlugin val3 = (BasePlugin)((instance is BasePlugin) ? instance : null); if (val3 != null) { var (val6, val7) = (KeyValuePair<ConfigDefinition, ConfigEntryBase>)(ref ((IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)val3.Config).FirstOrDefault((KeyValuePair<ConfigDefinition, ConfigEntryBase> k) => k.Key.Section.Equals(section, StringComparison.InvariantCultureIgnoreCase) && k.Key.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase))); if (val6 == (ConfigDefinition)null) { throw ctx.Error("Could not find property"); } try { object value2 = TomlTypeConverter.ConvertToValue(value, val7.SettingType); val7.SetSerializedValue(value); if (!val3.Config.SaveOnConfigSet) { val3.Config.Save(); } ctx.SysReply($"Set {val6.Key} = {value2}"); return; } catch (Exception) { throw ctx.Error($"Can not convert {value} to {val7.SettingType}"); } } } foreach (KeyValuePair<string, PluginInfo> plugin in ((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins) { ctx.SysReply("Found: " + plugin.Value.Metadata.GUID); } throw ctx.Error("Can not find that plugin"); } private static void DumpConfig(ICommandContext ctx, string guid, BasePlugin plugin) { ConfigFile config = plugin.Config; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Path: " + config.ConfigFilePath); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(33, 2, stringBuilder2); handler.AppendLiteral("Dumping config for "); handler.AppendFormatted(guid.Color("#f0f")); handler.AppendLiteral(" with "); handler.AppendFormatted(((IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)config).Count()); handler.AppendLiteral(" entries"); stringBuilder3.AppendLine(ref handler); foreach (IGrouping<string, KeyValuePair<ConfigDefinition, ConfigEntryBase>> item in from k in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)config group k by k.Key.Section into k orderby k.Key select k) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2); handler.AppendLiteral("["); handler.AppendFormatted(item.Key); handler.AppendLiteral("]"); stringBuilder4.AppendLine(ref handler); foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item2 in item) { item2.Deconstruct(out var key, out var value); ConfigDefinition val = key; ConfigEntryBase val2 = value; stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(3, 2, stringBuilder2); handler.AppendFormatted(val.Key.Color(Color.White)); handler.AppendLiteral(" = "); handler.AppendFormatted(val2.BoxedValue.ToString().Color(Color.LightGrey)); stringBuilder5.AppendLine(ref handler); } } ctx.SysPaginatedReply(stringBuilder); } } internal static class HelpCommands { private static readonly Regex _trailingLongDashRegex = new Regex("-\\d+$"); [Command("help-legacy", null, null, "Passes through a .help command that is compatible with other mods that don't use VCF.", null, false)] public static void HelpLegacy(ICommandContext ctx, string search = null) { ctx.SysReply("Attempting compatible .help " + search + " for non-VCF mods."); } [Command("help", null, null, null, null, false)] public static void HelpCommand(ICommandContext ctx, string search = null) { if (!string.IsNullOrEmpty(search)) { KeyValuePair<Assembly, Dictionary<CommandMetadata, List<string>>> assembly2 = CommandRegistry.AssemblyCommandMap.FirstOrDefault((KeyValuePair<Assembly, Dictionary<CommandMetadata, List<string>>> x) => x.Key.GetName().Name.StartsWith(search, StringComparison.OrdinalIgnoreCase)); if (assembly2.Value != null) { StringBuilder stringBuilder = new StringBuilder(); PrintAssemblyHelp(ctx, assembly2, stringBuilder); ctx.SysPaginatedReply(stringBuilder); return; } IEnumerable<KeyValuePair<CommandMetadata, List<string>>> source = CommandRegistry.AssemblyCommandMap.SelectMany((KeyValuePair<Assembly, Dictionary<CommandMetadata, List<string>>> x) => x.Value); IEnumerable<KeyValuePair<CommandMetadata, List<string>>> source2 = source.Where((KeyValuePair<CommandMetadata, List<string>> x) => string.Equals(x.Key.Attribute.Id, search, StringComparison.InvariantCultureIgnoreCase) || string.Equals(x.Key.Attribute.Name, search, StringComparison.InvariantCultureIgnoreCase) || x.Value.Contains<string>(search, StringComparer.InvariantCultureIgnoreCase)); source2 = source2.Where((KeyValuePair<CommandMetadata, List<string>> kvp) => CommandRegistry.CanCommandExecute(ctx, kvp.Key)); if (!source2.Any()) { throw ctx.Error("Could not find any commands for \"" + search + "\""); } StringBuilder stringBuilder2 = new StringBuilder(); foreach (KeyValuePair<CommandMetadata, List<string>> item2 in source2) { GenerateFullHelp(item2.Key, item2.Value, stringBuilder2); } ctx.SysPaginatedReply(stringBuilder2); return; } StringBuilder stringBuilder3 = new StringBuilder(); StringBuilder stringBuilder4 = stringBuilder3; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder4); handler.AppendLiteral("Listing "); handler.AppendFormatted(Format.B("all")); handler.AppendLiteral(" commands"); stringBuilder4.AppendLine(ref handler); foreach (KeyValuePair<Assembly, Dictionary<CommandMetadata, List<string>>> item3 in CommandRegistry.AssemblyCommandMap) { PrintAssemblyHelp(ctx, item3, stringBuilder3); } ctx.SysPaginatedReply(stringBuilder3); static void GenerateFullHelp(CommandMetadata command, List<string> aliases, StringBuilder sb) { StringBuilder stringBuilder5 = sb; StringBuilder stringBuilder6 = stringBuilder5; StringBuilder.AppendInterpolatedStringHandler handler2 = new StringBuilder.AppendInterpolatedStringHandler(4, 3, stringBuilder5); handler2.AppendFormatted(Format.B(command.Attribute.Name)); handler2.AppendLiteral(" ("); handler2.AppendFormatted(command.Attribute.Id); handler2.AppendLiteral(") "); handler2.AppendFormatted(command.Attribute.Description); stringBuilder6.AppendLine(ref handler2); sb.AppendLine(PrintShortHelp(command)); stringBuilder5 = sb; StringBuilder stringBuilder7 = stringBuilder5; handler2 = new StringBuilder.AppendInterpolatedStringHandler(2, 2, stringBuilder5); handler2.AppendFormatted(Format.B("Aliases").Underline()); handler2.AppendLiteral(": "); handler2.AppendFormatted(string.Join(", ", aliases).Italic()); stringBuilder7.AppendLine(ref handler2); IEnumerable<Type> enumerable = from t in command.Parameters.Select((ParameterInfo p) => p.ParameterType).Distinct() where t.IsEnum select t; foreach (Type item4 in enumerable) { stringBuilder5 = sb; StringBuilder stringBuilder8 = stringBuilder5; handler2 = new StringBuilder.AppendInterpolatedStringHandler(2, 2, stringBuilder5); handler2.AppendFormatted((item4.Name + " Values").Bold().Underline()); handler2.AppendLiteral(": "); handler2.AppendFormatted(string.Join(", ", Enum.GetNames(item4))); stringBuilder8.AppendLine(ref handler2); } IEnumerable<Type> enumerable2 = from p in command.Parameters.Select((ParameterInfo p) => p.ParameterType).Distinct() where CommandRegistry._converters.ContainsKey(p) select p; foreach (Type item5 in enumerable2) { object item = CommandRegistry._converters[item5].instance; if (item is IConverterUsage) { IConverterUsage converterUsage = item as IConverterUsage; stringBuilder5 = sb; StringBuilder stringBuilder9 = stringBuilder5; handler2 = new StringBuilder.AppendInterpolatedStringHandler(2, 2, stringBuilder5); handler2.AppendFormatted((item5.Name ?? "").Bold()); handler2.AppendLiteral(": "); handler2.AppendFormatted(converterUsage.Usage); stringBuilder9.AppendLine(ref handler2); } } } static void PrintAssemblyHelp(ICommandContext ctx, KeyValuePair<Assembly, Dictionary<CommandMetadata, List<string>>> assembly, StringBuilder sb) { string name = assembly.Key.GetName().Name; name = _trailingLongDashRegex.Replace(name, ""); sb.AppendLine(("Commands from " + name.Medium().Color(Color.Primary) + ":").Underline()); IEnumerable<CommandMetadata> enumerable3 = assembly.Value.Keys.Where((CommandMetadata c) => CommandRegistry.CanCommandExecute(ctx, c)); foreach (CommandMetadata item6 in enumerable3) { sb.AppendLine(PrintShortHelp(item6)); } } } internal static string PrintShortHelp(CommandMetadata command) { CommandAttribute attribute = command.Attribute; string text = (string.IsNullOrEmpty(command.GroupAttribute?.Name) ? string.Empty : (command.GroupAttribute.Name + " ")); string input = text + attribute.Name; string orGenerateUsage = GetOrGenerateUsage(command); string text2 = ".".Color(Color.Yellow); string text3 = input.Color(Color.White); return text2 + text3 + orGenerateUsage; } internal static string GetOrGenerateUsage(CommandMetadata command) { string text = command.Attribute.Usage; if (string.IsNullOrWhiteSpace(text)) { IEnumerable<string> values = command.Parameters.Select((ParameterInfo p) => (!p.HasDefaultValue) ? ("(" + p.Name + ")").Color(Color.LightGrey) : $"[{p.Name}={p.DefaultValue}]".Color(Color.DarkGreen)); text = string.Join(" ", values); } return (!string.IsNullOrWhiteSpace(text)) ? (" " + text) : string.Empty; } } } namespace VCF.Core.Basics { public class BasicAdminCheck : CommandMiddleware { public override bool CanExecute(ICommandContext ctx, CommandAttribute cmd, MethodInfo m) { return !cmd.AdminOnly || ctx.IsAdmin; } } public class RolePermissionMiddleware : CommandMiddleware { public override bool CanExecute(ICommandContext ctx, CommandAttribute command, MethodInfo method) { if (ctx.IsAdmin) { return true; } RoleRepository service = ctx.Services.GetService<RoleRepository>(); return service.CanUserExecuteCommand(ctx.Name, command.Id); } } public interface IRoleStorage { HashSet<string> Roles { get; } void SetCommandPermission(string command, HashSet<string> roleIds); void SetUserRoles(string userId, HashSet<string> roleIds); HashSet<string> GetCommandPermission(string command); HashSet<string> GetUserRoles(string userId); } public class RoleRepository { private IRoleStorage _storage; public HashSet<string> Roles => _storage.Roles; public RoleRepository(IRoleStorage storage) { _storage = storage; } public void AddUserToRole(string user, string role) { HashSet<string> hashSet = _storage.GetUserRoles(user) ?? new HashSet<string>(); hashSet.Add(role); _storage.SetUserRoles(user, hashSet); } public void RemoveUserFromRole(string user, string role) { HashSet<string> hashSet = _storage.GetUserRoles(user) ?? new HashSet<string>(); hashSet.Remove(role); _storage.SetUserRoles(user, hashSet); } public void AddRoleToCommand(string command, string role) { HashSet<string> hashSet = _storage.GetCommandPermission(command) ?? new HashSet<string>(); hashSet.Add(role); _storage.SetCommandPermission(command, hashSet); } public void RemoveRoleFromCommand(string command, string role) { HashSet<string> hashSet = _storage.GetCommandPermission(command) ?? new HashSet<string>(); hashSet.Remove(role); _storage.SetCommandPermission(command, hashSet); } public HashSet<string> ListUserRoles(string user) { return _storage.GetUserRoles(user); } public HashSet<string> ListCommandRoles(string command) { return _storage.GetCommandPermission(command); } public bool CanUserExecuteCommand(string user, string command) { HashSet<string> commandPermission = _storage.GetCommandPermission(command); if (commandPermission == null) { return false; } HashSet<string> userRoles = _storage.GetUserRoles(user); if (userRoles == null) { return false; } return commandPermission.Any(userRoles.Contains); } } public class MemoryRoleStorage : IRoleStorage { private Dictionary<string, HashSet<string>> _userRoles = new Dictionary<string, HashSet<string>>(); private Dictionary<string, HashSet<string>> _commandPermissions = new Dictionary<string, HashSet<string>>(); public HashSet<string> Roles => new HashSet<string>(); public void SetCommandPermission(string command, HashSet<string> roleIds) { foreach (string roleId in roleIds) { Roles.Add(roleId); } _commandPermissions[command] = roleIds; } public void SetUserRoles(string userId, HashSet<string> roleIds) { _userRoles[userId] = roleIds; } public HashSet<string> GetCommandPermission(string command) { return _commandPermissions.GetValueOrDefault(command); } public HashSet<string> GetUserRoles(string userId) { HashSet<string> value; return _userRoles.TryGetValue(userId, out value) ? value : new HashSet<string>(); } } public class RoleCommands { public record struct Role(string Name); public record struct User(string Id); public record struct Command(string Id); public class RoleConverter : CommandArgumentConverter<Role> { public override Role Parse(ICommandContext ctx, string input) { RoleRepository requiredService = ctx.Services.GetRequiredService<RoleRepository>(); if (requiredService.Roles.Contains(input)) { return new Role(input); } throw ctx.Error("Invalid role"); } } public class UserConverter : CommandArgumentConverter<User> { public override User Parse(ICommandContext ctx, string input) { return new User(input); } } private RoleRepository _roleRepository = new RoleRepository(new MemoryRoleStorage()); [Command("create", null, null, null, null, false)] public void CreateRole(ICommandContext ctx, string name) { _roleRepository.Roles.Add(name); } [Command("allow", null, null, null, null, false)] public void AllowCommand(ICommandContext ctx, Role role, Command command) { _roleRepository.AddRoleToCommand(command.Id, role.Name); } [Command("deny", null, null, null, null, false)] public void DenyCommand(ICommandContext ctx, Role role, Command command) { _roleRepository.RemoveRoleFromCommand(command.Id, role.Name); } [Command("assign", null, null, null, null, false)] public void AssignUserToRole(ICommandContext ctx, User user, Role role) { _roleRepository.AddUserToRole(user.Id, role.Name); } [Command("unassign", null, null, null, null, false)] public void UnassignUserFromRole(ICommandContext ctx, User user, Role role) { _roleRepository.RemoveUserFromRole(user.Id, role.Name); } [Command("list", null, null, null, null, false)] public void ListRoles(ICommandContext ctx) { ctx.Reply("Roles: " + string.Join(", ", _roleRepository.Roles)); } [Command("list user", null, null, null, null, false)] public void ListRoles(ICommandContext ctx, User user) { ctx.Reply("Roles: " + string.Join(", ", _roleRepository.ListUserRoles(user.Id))); } [Command("list command", null, null, null, null, false)] public void ListCommands(ICommandContext ctx, Command command) { ctx.Reply("Roles: " + string.Join(", ", _roleRepository.ListCommandRoles(command.Id))); } } }