diff --git a/VpSharp/src/ClientExtensions/VirtualParadiseClientExtension.cs b/VpSharp/src/ClientExtensions/VirtualParadiseClientExtension.cs
new file mode 100644
index 0000000..20c4d76
--- /dev/null
+++ b/VpSharp/src/ClientExtensions/VirtualParadiseClientExtension.cs
@@ -0,0 +1,33 @@
+using VpSharp.EventData;
+
+namespace VpSharp.ClientExtensions;
+
+///
+/// Represents the base class for extensions to the .
+///
+public abstract class VirtualParadiseClientExtension
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The owning client.
+ protected VirtualParadiseClientExtension(VirtualParadiseClient client)
+ {
+ Client = client;
+ }
+
+ ///
+ /// Gets the client to which this extension is registered.
+ ///
+ /// The owning client.
+ public VirtualParadiseClient Client { get; }
+
+ ///
+ /// Called when a chat message is received.
+ ///
+ /// An object containing event data.
+ protected internal virtual Task OnMessageReceived(MessageReceivedEventArgs args)
+ {
+ return Task.CompletedTask;
+ }
+}
diff --git a/VpSharp/src/VirtualParadiseClient.Extensions.cs b/VpSharp/src/VirtualParadiseClient.Extensions.cs
new file mode 100644
index 0000000..d3d9401
--- /dev/null
+++ b/VpSharp/src/VirtualParadiseClient.Extensions.cs
@@ -0,0 +1,68 @@
+using System.Globalization;
+using System.Reflection;
+using VpSharp.ClientExtensions;
+
+namespace VpSharp;
+
+public sealed partial class VirtualParadiseClient
+{
+ private readonly List _extensions = new();
+
+ ///
+ /// Adds an extension to this client.
+ ///
+ /// The arguments to pass to the extension constructor.
+ /// The type of the extension to add.
+ /// A new instance of the specified extension.
+ ///
+ /// is abstract.
+ ///
+ public T AddExtension(params object?[]? arguments) where T : VirtualParadiseClientExtension
+ {
+ return (AddExtension(typeof(T), arguments) as T)!;
+ }
+
+ ///
+ /// Adds an extension to this client.
+ ///
+ /// The type of the extension to add.
+ /// The arguments to pass to the extension constructor.
+ /// A new instance of the specified extension.
+ /// is .
+ ///
+ /// does not inherit from .
+ /// -or-
+ /// is abstract.
+ ///
+ public VirtualParadiseClientExtension AddExtension(Type type, params object?[]? arguments)
+ {
+ ArgumentNullException.ThrowIfNull(type);
+
+ if (!type.IsSubclassOf(typeof(VirtualParadiseClientExtension)))
+ {
+ throw new ArgumentException($"Type must inherit from {typeof(VirtualParadiseClientExtension)}");
+ }
+
+ if (type.IsAbstract)
+ {
+ throw new ArgumentException("Extension type cannot be abstract");
+ }
+
+ object?[] argumentsActual = {this};
+ if (arguments is not null)
+ {
+ argumentsActual = argumentsActual.Concat(arguments).ToArray();
+ }
+
+ const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
+ object? instance = Activator.CreateInstance(type, bindingFlags, null, argumentsActual, CultureInfo.InvariantCulture);
+ if (instance is not VirtualParadiseClientExtension extension)
+ {
+ var innerException = new Exception($"Could not instantiate {type}");
+ throw new TypeInitializationException(type.FullName, innerException);
+ }
+
+ _extensions.Add(extension);
+ return extension;
+ }
+}
diff --git a/VpSharp/src/VirtualParadiseClient.NativeEvents.cs b/VpSharp/src/VirtualParadiseClient.NativeEvents.cs
index c3b001b..3f5b068 100644
--- a/VpSharp/src/VirtualParadiseClient.NativeEvents.cs
+++ b/VpSharp/src/VirtualParadiseClient.NativeEvents.cs
@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using System.Drawing;
using System.Numerics;
using System.Threading.Channels;
+using VpSharp.ClientExtensions;
using VpSharp.Entities;
using VpSharp.EventData;
using VpSharp.Internal;
@@ -79,6 +80,11 @@ public sealed partial class VirtualParadiseClient
var args = new MessageReceivedEventArgs(message);
RaiseEvent(MessageReceived, args);
+
+ foreach (VirtualParadiseClientExtension extension in _extensions)
+ {
+ extension.OnMessageReceived(args);
+ }
}
private async void OnAvatarAddNativeEvent(IntPtr sender)