diff --git a/Pilz.Plugins.Advanced/Extensions.cs b/Pilz.Plugins.Advanced/Extensions.cs
new file mode 100644
index 0000000..7b4a1b6
--- /dev/null
+++ b/Pilz.Plugins.Advanced/Extensions.cs
@@ -0,0 +1,62 @@
+using SM64RomManager.Functions;
+using System.Drawing;
+using System.Reflection;
+using Telerik.WinControls;
+using Telerik.WinControls.UI;
+
+namespace Pilz.Plugins.Advanced
+{
+ public static class Extensions
+ {
+ public static Icon? ToIcon(this Image image)
+ {
+ if (image is Bitmap bitmap)
+ return Icon.FromHandle(bitmap.GetHicon());
+ return null;
+ }
+
+ public static RadMenuItem GetAsItem(this PluginModule module, bool addDefaultHandler = true)
+ {
+ var item = new RadMenuItem
+ {
+ Text = module.Name,
+ SvgImage = module.Icon,
+ Tag = module,
+ Visibility = module.Visible ? ElementVisibility.Visible : ElementVisibility.Collapsed
+ };
+
+ if (addDefaultHandler)
+ item.Click += RadMenuItem_RMMethod_Click;
+
+ return item;
+ }
+
+ public static RadMenuItem GetAsItem(this PluginFunction function, bool addDefaultHandler = true)
+ {
+ var item = new RadMenuItem
+ {
+ Text = function.Name,
+ SvgImage = function.Icon,
+ Tag = function,
+ Visibility = function.Enabled ? ElementVisibility.Visible : ElementVisibility.Collapsed
+ };
+
+ if (addDefaultHandler)
+ item.Click += RadMenuItem_RMFunction_Click;
+
+ return item;
+ }
+
+ private static void RadMenuItem_RMMethod_Click(object? sender, EventArgs e)
+ {
+ if (sender is RadMenuItem item && item.Tag is PluginModule function)
+ function.ShowUI();
+ }
+
+ private static void RadMenuItem_RMFunction_Click(object? sender, EventArgs e)
+ {
+ if (sender is RadMenuItem item && item.Tag is PluginFunction function)
+ function.Execute();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Pilz.Plugins.Advanced/Pilz.Plugins.Advanced.csproj b/Pilz.Plugins.Advanced/Pilz.Plugins.Advanced.csproj
new file mode 100644
index 0000000..f8254b8
--- /dev/null
+++ b/Pilz.Plugins.Advanced/Pilz.Plugins.Advanced.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net6.0-windows
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pilz.Plugins.Advanced/PluginFunction.cs b/Pilz.Plugins.Advanced/PluginFunction.cs
new file mode 100644
index 0000000..d964f7f
--- /dev/null
+++ b/Pilz.Plugins.Advanced/PluginFunction.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Telerik.WinControls;
+
+namespace Pilz.Plugins.Advanced
+{
+ public abstract class PluginFunction
+ {
+ public string Type { get; init; }
+ public string? Name { get; init; }
+ public RadSvgImage? Icon { get; set; }
+ public bool Enabled { get; set; } = true;
+
+ protected PluginFunction(string functionType)
+ {
+ Type = functionType;
+ }
+
+ protected PluginFunction(string functionType, string functionName) : this(functionType)
+ {
+ Name = functionName;
+ }
+
+ public object? Execute()
+ {
+ return Execute((PluginFunctionParameter?)null);
+ }
+
+ public T? Execute(params object?[]? @params)
+ {
+ return Execute(new PluginFunctionSimpleParamter(@params));
+ }
+
+ public object? Execute(params object?[]? @params)
+ {
+ return Execute(new PluginFunctionSimpleParamter(@params));
+ }
+
+ public T? Execute(PluginFunctionSimpleParamter? @params)
+ {
+ if (Execute(@params) is T result)
+ return result;
+ return default;
+ }
+
+ public object? Execute(PluginFunctionParameter? @params)
+ {
+#if !DEBUG
+ try
+ {
+#endif
+ return ExecuteFunction(@params);
+#if !DEBUG
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+#endif
+ }
+
+ protected abstract object? ExecuteFunction(PluginFunctionParameter? @params);
+ }
+}
diff --git a/Pilz.Plugins.Advanced/PluginFunctionController.cs b/Pilz.Plugins.Advanced/PluginFunctionController.cs
new file mode 100644
index 0000000..8891810
--- /dev/null
+++ b/Pilz.Plugins.Advanced/PluginFunctionController.cs
@@ -0,0 +1,60 @@
+namespace Pilz.Plugins.Advanced
+{
+ public sealed class PluginFunctionController
+ {
+ public static PluginFunctionController Instance { get; private set; } = new();
+
+ private readonly List functions = new();
+
+ public IReadOnlyCollection Functions => functions.AsReadOnly();
+
+ public void RegisterFunction(PluginFunction function)
+ {
+ if (!functions.Contains(function))
+ functions.Add(function);
+ }
+
+ public void UnregisterFunction(PluginFunction function)
+ {
+ functions.Remove(function);
+ }
+
+ public IEnumerable GetFunctions(string functionType)
+ {
+ return functions.Where(n => n.Type == functionType);
+ }
+
+ public void ExecuteAll(string functionType)
+ {
+ foreach (var function in GetFunctions(functionType))
+ function.Execute();
+ }
+
+ public void ExecuteAll(string functionType, params object?[]? @params)
+ {
+ foreach (var function in GetFunctions(functionType))
+ function.Execute(@params);
+ }
+
+ public void ExecuteAll(string functionType, PluginFunctionParameter @params)
+ {
+ foreach (var function in GetFunctions(functionType))
+ function.Execute(@params);
+ }
+
+ public IEnumerable