From 65d5eba3bf15615fb55a481ede7508894dbee151 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Thu, 28 May 2026 21:55:53 -0500 Subject: [PATCH] Rework BAUModdedSupport --- .../ModdedSupportBAUEventExample.cs | 65 ----- .../ModdedSupportExampleClass.cs | 59 +++++ .../ModdedSupportPluginExample.cs | 39 --- src/BAUPlugin.cs | 4 +- src/Data/Config/BAUConfigs.cs | 2 +- src/Modules/Support/BAUModdedSupportEvents.cs | 140 +---------- src/Modules/Support/BAUModdedSupportFlags.cs | 42 +--- src/Modules/Support/BAUSupportEvent.cs | 229 ++++++++++++++++++ src/Modules/Support/BAUSupportVar.cs | 78 ++++++ 9 files changed, 391 insertions(+), 267 deletions(-) delete mode 100644 examples/moddedsupport/ModdedSupportBAUEventExample.cs create mode 100644 examples/moddedsupport/ModdedSupportExampleClass.cs delete mode 100644 examples/moddedsupport/ModdedSupportPluginExample.cs create mode 100644 src/Modules/Support/BAUSupportEvent.cs create mode 100644 src/Modules/Support/BAUSupportVar.cs diff --git a/examples/moddedsupport/ModdedSupportBAUEventExample.cs b/examples/moddedsupport/ModdedSupportBAUEventExample.cs deleted file mode 100644 index 1eb2bdf..0000000 --- a/examples/moddedsupport/ModdedSupportBAUEventExample.cs +++ /dev/null @@ -1,65 +0,0 @@ -using BepInEx.Configuration; -using BepInEx.Unity.IL2CPP; - -namespace BetterAmongUs.Examples; - -/// -/// Example event handler class demonstrating how to respond to BetterAmongUs events. -/// This class must be instantiated and assigned to the BAUEvents field in the plugin. -/// -/// -/// Each method in this class corresponds to a different lifecycle event in BetterAmongUs. -/// Methods are optional - only implement the ones your plugin needs. -/// -internal class ModdedSupportBAUEventExample -{ - /// - /// Called when BetterAmongUs is loading. Can be used to prevent BAU from loading. - /// - /// The BetterAmongUs plugin instance. - /// - /// Return true to allow BAU to continue loading, or false to prevent BAU from loading. - /// This can be useful if your plugin has compatibility issues with specific BAU versions. - /// - public bool OnBAULoad(BasePlugin bauPlugin) - { - return true; - } - - /// - /// Called when BetterAmongUs game options have been loaded. - /// Allows plugins to read or modify BAU game options. - /// - /// - /// Array of game options objects from BetterAmongUs. - /// The exact type of these objects depends on BAU's internal implementation. - /// Common option types to check for: - /// Modules.OptionItems.OptionItem, - /// Modules.OptionItems.OptionItem{T}, - /// Modules.OptionItems.OptionCheckboxItem, - /// Modules.OptionItems.OptionFloatItem, - /// Modules.OptionItems.OptionIntItem, - /// Modules.OptionItems.OptionPercentItem, - /// Modules.OptionItems.OptionPlayerItem, - /// Modules.OptionItems.OptionStringItem, - /// - public void OnBAUOptionsLoaded(object[] options) - { - } - - /// - /// Called when BetterAmongUs configuration entries have been loaded. - /// Allows plugins to read or modify BAU's BepInEx configuration entries. - /// - /// - /// Array of BepInEx configuration entries from BetterAmongUs. - /// These can be cast to ConfigEntry<T> to access their values. - /// - /// - /// This is useful for plugins that need to interact with BAU's configuration, - /// such as reading default values or adding validation to certain settings. - /// - public void OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs) - { - } -} \ No newline at end of file diff --git a/examples/moddedsupport/ModdedSupportExampleClass.cs b/examples/moddedsupport/ModdedSupportExampleClass.cs new file mode 100644 index 0000000..8e63013 --- /dev/null +++ b/examples/moddedsupport/ModdedSupportExampleClass.cs @@ -0,0 +1,59 @@ +using BepInEx.Configuration; +using BepInEx.Unity.IL2CPP; +using BetterAmongUs.Helpers; +using System.ComponentModel; + +namespace BetterAmongUs.Examples; + +/// +/// Example class demonstrating how to create modded support for BetterAmongUs. +/// +/// +/// All methods and fields must be static and decorated with the appropriate +/// to be discovered by BetterAmongUs through reflection. +/// +internal class ModdedSupportExampleClass +{ + /// + /// Array of flags to control BetterAmongUs behavior. + /// Check BAUModdedSupportFlags for available flag constants. + /// + [Category("bau:flags")] + public static string[] BAUFlags = []; + + /// + /// Called when BetterAmongUs is loading. Return false to prevent BAU from loading. + /// + /// The BetterAmongUs plugin instance. + /// True to allow BAU to load, false to prevent it. + [Category("bau:event.bau_load")] + public static bool OnBAULoad(BasePlugin bauPlugin) + { + Logger_.Error("TEST 1"); + return true; + } + + /// + /// Called when BetterAmongUs game options have been loaded. + /// + /// + /// Array of game option objects from BetterAmongUs. Common types include: + /// OptionItem, OptionItem{T}, OptionCheckboxItem, OptionFloatItem, + /// OptionIntItem, OptionPercentItem, OptionPlayerItem, OptionStringItem + /// + [Category("bau:event.options_load")] + public static void OnBAUOptionsLoaded(object[] options) + { + Logger_.Error("TEST 2"); + } + + /// + /// Called when BetterAmongUs configuration entries have been loaded. + /// + /// Array of BepInEx ConfigEntryBase objects from BetterAmongUs. + [Category("bau:event.configs_load")] + public static void OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs) + { + Logger_.Error("TEST 3"); + } +} \ No newline at end of file diff --git a/examples/moddedsupport/ModdedSupportPluginExample.cs b/examples/moddedsupport/ModdedSupportPluginExample.cs deleted file mode 100644 index 102b19a..0000000 --- a/examples/moddedsupport/ModdedSupportPluginExample.cs +++ /dev/null @@ -1,39 +0,0 @@ -using BepInEx.Unity.IL2CPP; - -namespace BetterAmongUs.Examples; - -/// -/// Example plugin demonstrating how to integrate with BetterAmongUs modded support system. -/// Shows the structure required for a plugin to declare BAU events and flags. -/// -/// -/// This example shows a minimal plugin that implements both event handlers and flags. -/// In a real implementation, you would add your plugin's actual logic in the Load() method. -/// -internal class ModdedSupportPluginExample : BasePlugin -{ - /// - /// Event handler instance for BetterAmongUs events. - /// This field must be named exactly "BAUEvents" to be detected by the reflection system. - /// - public static ModdedSupportBAUEventExample BAUEvents = new(); - - /// - /// Array of flags to control BetterAmongUs behavior. - /// This field must be named exactly "BAUFlags" to be detected by the reflection system. - /// - /// - /// Flags can disable specific BAU features or modify its behavior. - /// Check Modules.Support.BAUModdedSupportFlags for available flag constants. - /// - public static string[] BAUFlags = []; - - /// - /// Main plugin load method. - /// In a real implementation, this is where your plugin initialization would occur. - /// - public override void Load() - { - // Plugin initialization logic would go here - } -} \ No newline at end of file diff --git a/src/BAUPlugin.cs b/src/BAUPlugin.cs index 6f85cbc..77dfd50 100644 --- a/src/BAUPlugin.cs +++ b/src/BAUPlugin.cs @@ -146,7 +146,7 @@ internal class BAUPlugin : BasePlugin /// private void OnChainloaderFinished() { - if (!BAUModdedSupportEvents.InvokeAll_OnBAULoad(this)) + if (BAUModdedSupportEvents.OnBAULoadEvent.InvokeAll(this).Any(b => b == false)) return; BAUModdedSupportFlags.Initialize(); @@ -157,7 +157,7 @@ internal class BAUPlugin : BasePlugin Translator.Initialize(); Harmony.PatchAll(); GameSettingsPatch.SetupSettings(true); - BAUModdedSupportEvents.InvokeAll_OnBAUOptionsLoaded([.. OptionItem.AllOptions.Cast()]); + BAUModdedSupportEvents.OnBAUOptionsLoadedEvent.InvokeAll([.. OptionItem.AllOptions.Cast()]); InstanceAttribute.RegisterAll(); OutfitData.Initialize(); diff --git a/src/Data/Config/BAUConfigs.cs b/src/Data/Config/BAUConfigs.cs index d02e544..f052c5c 100644 --- a/src/Data/Config/BAUConfigs.cs +++ b/src/Data/Config/BAUConfigs.cs @@ -88,7 +88,7 @@ internal static class BAUConfigs /// internal static void LoadConfigs() { - BAUModdedSupportEvents.InvokeAll_OnBAUConfigEntriesLoaded([ + BAUModdedSupportEvents.OnBAUConfigEntriesLoadedEvent.InvokeAll([ AntiCheat, SendBetterRpc, BetterNotifications, ForceOwnLanguage, ChatDarkMode, ChatInGameplay, LobbyPlayerInfo, DisableLobbyTheme, UnlockFPS, ShowFPS, diff --git a/src/Modules/Support/BAUModdedSupportEvents.cs b/src/Modules/Support/BAUModdedSupportEvents.cs index e15ac25..dd61b16 100644 --- a/src/Modules/Support/BAUModdedSupportEvents.cs +++ b/src/Modules/Support/BAUModdedSupportEvents.cs @@ -1,6 +1,5 @@ using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; -using System.Reflection; namespace BetterAmongUs.Modules.Support; @@ -8,141 +7,20 @@ namespace BetterAmongUs.Modules.Support; /// Provides a system for modded plugins to interact with BetterAmongUs through reflection-based events. /// Allows other plugins to define event handlers without direct assembly references. /// -/// -/// Plugins can implement event handlers by creating a class with specific method signatures. -/// The system automatically discovers and invokes these handlers at runtime. -/// -public class BAUModdedSupportEvents +public static class BAUModdedSupportEvents { - // ============================================ - // Method Structures - // ============================================ + /// + /// Event triggered when BetterAmongUs is loading. + /// + internal static readonly BAUSupportEvent OnBAULoadEvent = new("bau:event.bau_load"); /// - /// Default implementation of the OnBAULoad event handler. + /// Event triggered when BetterAmongUs game options have been loaded. /// - /// The BetterAmongUs plugin instance. - /// Always returns true to indicate successful loading. - public bool OnBAULoad(BasePlugin bauPlugin) => true; + internal static readonly BAUSupportEvent OnBAUOptionsLoadedEvent = new("bau:event.options_load"); /// - /// Default implementation of the OnBAUOptionsLoaded event handler. + /// Event triggered when BetterAmongUs configuration entries have been loaded. /// - /// The loaded game options from BetterAmongUs. - public void OnBAUOptionsLoaded(object[] options) { } - - /// - /// Default implementation of the OnBAUConfigEntriesLoaded event handler. - /// - /// An array of BepInEx configuration entries from BetterAmongUs. - public void OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs) { } - - // ============================================ - // Method Structures - // ============================================ - - /// - /// Invokes the OnBAULoad event handler for all loaded plugins. - /// - /// The BetterAmongUs plugin instance. - /// - /// Returns false if any plugin's OnBAULoad handler returns false, otherwise returns true. - /// Returns true if no plugins implement the handler. - /// - internal static bool InvokeAll_OnBAULoad(BasePlugin bauPlugin) - { - foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values) - { - var plugin = (BasePlugin)pluginInfo.Instance; - if (InvokePluginMethod(plugin, nameof(OnBAULoad), defaultValue: true, bauPlugin) == false) - { - return false; - } - } - - return true; - } - - /// - /// Invokes the OnBAUOptionsLoaded event handler for all loaded plugins. - /// - /// The loaded game options from BetterAmongUs. - internal static void InvokeAll_OnBAUOptionsLoaded(object[] options) - { - foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values) - { - var plugin = (BasePlugin)pluginInfo.Instance; - InvokePluginMethod(plugin, nameof(OnBAUOptionsLoaded), parameters: options); - } - } - - /// - /// Invokes the OnBAUConfigEntriesLoaded event handler for all loaded plugins. - /// - /// An array of BepInEx configuration entries from BetterAmongUs. - internal static void InvokeAll_OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs) - { - foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values) - { - var plugin = (BasePlugin)pluginInfo.Instance; - InvokePluginMethod(plugin, nameof(OnBAUConfigEntriesLoaded), parameters: configs); - } - } - - /// - /// Invokes a specific method on a plugin's event class using reflection. - /// - /// The expected return type of the method. - /// The plugin instance to invoke the method on. - /// The name of the method to invoke. - /// The value to return if the method cannot be invoked. - /// The parameters to pass to the method. - /// - /// The result of the method invocation, or if the method - /// cannot be found, invocation fails, or the return type doesn't match . - /// - private static T? InvokePluginMethod(BasePlugin plugin, string methodName, T? defaultValue = default, params object[] parameters) - { - var eventClass = GetEventClass(plugin); - if (eventClass == null) return defaultValue; - - var method = eventClass.GetType().GetMethod(methodName, - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - - if (method == null) return defaultValue; - - try - { - var result = method.Invoke(eventClass, parameters); - - return result is T typedResult ? typedResult : defaultValue; - } - catch - { - return defaultValue; - } - } - - /// - /// Retrieves the event class instance from a plugin using reflection. - /// - /// The plugin instance to search for the event class. - /// - /// The event class instance if found, otherwise null. - /// - private static object? GetEventClass(BasePlugin plugin) - { - const string EventFieldName = "BAUEvents"; - - var field = plugin.GetType().GetField(EventFieldName, - BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); - - if (field == null) return null; - - var value = field.IsStatic - ? field.GetValue(null) - : field.GetValue(plugin); - - return value?.GetType().IsClass == true ? value : null; - } + internal static readonly BAUSupportEvent OnBAUConfigEntriesLoadedEvent = new("bau:event.configs_load"); } \ No newline at end of file diff --git a/src/Modules/Support/BAUModdedSupportFlags.cs b/src/Modules/Support/BAUModdedSupportFlags.cs index 92106f0..e90adaa 100644 --- a/src/Modules/Support/BAUModdedSupportFlags.cs +++ b/src/Modules/Support/BAUModdedSupportFlags.cs @@ -1,9 +1,6 @@ #pragma warning disable CA2211 -using BepInEx; using BepInEx.Unity.IL2CPP; -using BetterAmongUs.Helpers; -using System.Reflection; namespace BetterAmongUs.Modules.Support; @@ -202,6 +199,8 @@ public static class BAUModdedSupportFlags private static readonly HashSet _flags = []; private static bool _initialized = false; + private static readonly BAUSupportVar BAUSupportFlags = new("bau:flags"); + /// /// Initializes the modded support system by collecting flags from all loaded plugins. /// @@ -211,38 +210,23 @@ public static class BAUModdedSupportFlags return; _initialized = true; + List flags = []; foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values) { - TryGetFlags((BasePlugin)pluginInfo.Instance); - } - } + if (pluginInfo == null) + continue; - /// - /// Attempts to extract BAUFlags from a loaded plugin's fields. - /// - /// The plugin instance to extract flags from. - private static void TryGetFlags(BasePlugin plugin) - { - var field = plugin.GetType().GetField("BAUFlags", - BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); - - if (field == null) - return; - - var value = field.IsStatic ? field.GetValue(null) : field.GetValue(plugin); - - if (value is not IEnumerable strings) - return; - - var pluginName = plugin.GetType().GetCustomAttribute()?.Name ?? plugin.GetType().Name; - - foreach (var flag in strings) - { - if (_flags.Add(flag)) + var modFlags = BAUSupportFlags.GetValue(pluginInfo); + if (modFlags != null) { - Logger_.Log($"Loaded '{flag}' flag from {pluginName}", "BAUModdedSupport"); + flags.AddRange(modFlags); } } + + foreach (var flag in flags.ToHashSet()) + { + AddFlag(flag); + } } /// diff --git a/src/Modules/Support/BAUSupportEvent.cs b/src/Modules/Support/BAUSupportEvent.cs new file mode 100644 index 0000000..3f9bbc7 --- /dev/null +++ b/src/Modules/Support/BAUSupportEvent.cs @@ -0,0 +1,229 @@ +using BepInEx; +using BepInEx.Unity.IL2CPP; +using BetterAmongUs.Helpers; +using System.ComponentModel; +using System.Reflection; + +namespace BetterAmongUs.Modules.Support; + +/// +/// Base class for reflection-based event systems that allow plugins to interact with BetterAmongUs. +/// Provides infrastructure for discovering and invoking static methods decorated with specific +/// flags across all loaded plugins. +/// +internal abstract class BAUSupportEvent(string attributeFlag) +{ + /// + /// Gets the expected parameter types for the event handler method. + /// + protected abstract Type[] ArgTypes { get; } + + /// + /// The category attribute flag used to identify target methods. + /// + protected string _attributeFlag = attributeFlag; + + /// + /// Cache of discovered methods per plugin to avoid repeated reflection lookups. + /// + protected readonly Dictionary _methods = []; + + /// + /// Invokes the event handler on all loaded plugins and collects their return values. + /// + /// The expected return type of the event handler. + /// The arguments to pass to the event handler method. + /// + /// An array containing the return values from all plugins that have a matching event handler. + /// Returns default(T) for plugins without a handler or if the handler returns null. + /// + protected T?[] InvokeMethodAll(params object[] args) + { + List list = []; + foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values) + { + if (pluginInfo == null) + continue; + + list.Add(InvokeMethod(pluginInfo, args)); + } + + return [.. list]; + } + + /// + /// Invokes the event handler on a specific plugin. + /// + /// The expected return type of the event handler. + /// The plugin to invoke the handler on. + /// The arguments to pass to the event handler method. + /// + /// The return value from the event handler, or default(T) if no handler is found + /// or an exception occurs during invocation. + /// + protected T? InvokeMethod(PluginInfo pluginInfo, params object[] args) + { + if (pluginInfo == null || pluginInfo.Instance == null) + return default; + + if (!_methods.TryGetValue(pluginInfo, out var method)) + { + method = FindMethod(pluginInfo); + _methods[pluginInfo] = method; + } + + if (method == null) + return default; + + try + { + return (T?)method.Invoke(null, args); + } + catch (Exception ex) + { + Logger_.Error($"Invoking event '{_attributeFlag}' for {pluginInfo.GetType().Name}: {ex}"); + return default; + } + } + + /// + /// Finds a static method in the plugin's assembly that matches the configured attribute flag and parameter types. + /// + /// The plugin to search for matching methods. + /// + /// A representing the matching method, or null if no matching method is found. + /// + private MethodInfo? FindMethod(PluginInfo pluginInfo) + { + var assembly = pluginInfo.Instance.GetType().Assembly; + var expectedArgTypes = ArgTypes; + + foreach (var type in assembly.GetTypes()) + { + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) + { + var attr = method.GetCustomAttribute(); + if (attr == null || attr.Category != _attributeFlag) + continue; + + var parameters = method.GetParameters(); + if (parameters.Length != expectedArgTypes.Length) + continue; + + bool match = true; + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType != expectedArgTypes[i]) + { + match = false; + break; + } + } + + if (match) + { + return method; + } + } + } + + return null; + } +} + + +/// +/// Represents an event with no parameters that plugins can handle. +/// +/// The return type of the event handler. +internal class BAUSupportEvent(string attributeFlag) : BAUSupportEvent(attributeFlag) +{ + /// + protected override Type[] ArgTypes => []; + + /// + /// Invokes the event handler on a specific plugin. + /// + /// The plugin to invoke the handler on. + /// The return value from the event handler, or default(ReturnT) if not found. + internal ReturnT? Invoke(PluginInfo pluginInfo) + { + return InvokeMethod(pluginInfo); + } + + /// + /// Invokes the event handler on all loaded plugins and collects their return values. + /// + /// An array containing the return values from all plugins that have a matching event handler. + internal ReturnT?[] InvokeAll() + { + return InvokeMethodAll(); + } +} + +/// +/// Represents an event with one parameter that plugins can handle. +/// +/// The return type of the event handler. +/// The type of the first parameter. +internal sealed class BAUSupportEvent(string attributeFlag) : BAUSupportEvent(attributeFlag) +{ + /// + protected override Type[] ArgTypes => [typeof(Arg1)]; + + /// + /// Invokes the event handler on a specific plugin. + /// + /// The plugin to invoke the handler on. + /// The first argument to pass to the event handler. + /// The return value from the event handler, or default(ReturnT) if not found. + internal ReturnT? Invoke(PluginInfo pluginInfo, Arg1 arg1) + { + return InvokeMethod(pluginInfo, arg1); + } + + /// + /// Invokes the event handler on all loaded plugins and collects their return values. + /// + /// The first argument to pass to all event handlers. + /// An array containing the return values from all plugins that have a matching event handler. + internal ReturnT?[] InvokeAll(Arg1 arg1) + { + return InvokeMethodAll(arg1); + } +} + +/// +/// Represents an event with two parameters that plugins can handle. +/// +/// The return type of the event handler. +/// The type of the first parameter. +/// The type of the second parameter. +internal sealed class BAUSupportEvent(string attributeFlag) : BAUSupportEvent(attributeFlag) +{ + /// + protected override Type[] ArgTypes => [typeof(Arg1), typeof(Arg2)]; + + /// + /// Invokes the event handler on a specific plugin. + /// + /// The plugin to invoke the handler on. + /// The first argument to pass to the event handler. + /// The second argument to pass to the event handler. + /// The return value from the event handler, or default(ReturnT) if not found. + internal ReturnT? Invoke(PluginInfo pluginInfo, Arg1 arg1, Arg2 arg2) + { + return InvokeMethod(pluginInfo, arg1, arg2); + } + + /// + /// Invokes the event handler on all loaded plugins and collects their return values. + /// + /// The first argument to pass to all event handlers. + /// The second argument to pass to all event handlers. + /// An array containing the return values from all plugins that have a matching event handler. + internal ReturnT?[] InvokeAll(Arg1 arg1, Arg2 arg2) + { + return InvokeMethodAll(arg1, arg2); + } +} \ No newline at end of file diff --git a/src/Modules/Support/BAUSupportVar.cs b/src/Modules/Support/BAUSupportVar.cs new file mode 100644 index 0000000..38cd594 --- /dev/null +++ b/src/Modules/Support/BAUSupportVar.cs @@ -0,0 +1,78 @@ +using BepInEx; +using System.ComponentModel; +using System.Reflection; + +namespace BetterAmongUs.Modules.Support; + +/// +/// Provides a mechanism to retrieve values from plugins through reflection-based property/field access. +/// +/// The type of value to retrieve from the target plugin. +internal sealed class BAUSupportVar(string attributeFlag) +{ + private readonly string _attributeFlag = attributeFlag; + private readonly Dictionary?> _getters = []; + + /// + /// Retrieves the value from the specified plugin that matches the configured attribute flag. + /// + /// The plugin information for the target plugin. + /// + /// The value of the field or property decorated with the matching , + /// or default(T) if no matching member is found or the plugin instance is null. + /// + internal T? GetValue(PluginInfo pluginInfo) + { + if (pluginInfo == null || pluginInfo.Instance == null) + return default; + + if (!_getters.TryGetValue(pluginInfo, out var getter)) + { + getter = FindGetter(pluginInfo); + if (getter == null) + { + getter = () => default; + } + _getters[pluginInfo] = getter; + } + + return getter(); + } + + /// + /// Locates a static field or property in the plugin's assembly that is decorated with the + /// configured . + /// + /// The plugin information for the target plugin. + /// + /// A function delegate that retrieves the value from the matching member, + /// or null if no matching member is found. + /// + private Func? FindGetter(PluginInfo pluginInfo) + { + foreach (var type in pluginInfo.Instance.GetType().Assembly.GetTypes()) + { + // Search for matching static fields + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) + { + var attr = field.GetCustomAttribute(); + if (attr != null && attr.Category == _attributeFlag) + { + return () => (T?)field.GetValue(null); + } + } + + // Search for matching static properties + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) + { + var attr = prop.GetCustomAttribute(); + if (attr != null && attr.Category == _attributeFlag) + { + return () => (T?)prop.GetValue(null); + } + } + } + + return null; + } +} \ No newline at end of file