Class Library in your .csproj so the compiler emits a .dll instead of an .exe.Build powerful plugins for Raton in minutes. Extend the client with compiled C# assemblies loaded at runtime.
Welcome to the Raton Plugin Guide. This guide walks you through everything needed to create, test, and publish plugins that extend Raton's functionality. Plugins are compiled C# assemblies loaded at runtime — they can receive arguments from the UI and communicate back through a structured packet system.
[PluginInfo] and implement Main.dll into Raton's /plugins folder — doneYour plugin is a standard .NET Class Library project. Raton discovers it as a compiled DLL. Minimum layout:
Class Library in your .csproj so the compiler emits a .dll instead of an .exe.Every plugin class must be decorated with [PluginInfo]. Raton reads this to register your plugin in the UI and display a description to the user.
using System;
[AttributeUsage(AttributeTargets.Class)]
public class PluginInfoAttribute : Attribute
{
public string Description { get; }
public string Provider { get; }
public PluginInfoAttribute(string description)
{
Provider = "Raton";
Description = description;
}
}
Provider = "Raton". Raton uses this field to verify the plugin origin.Usage on your own class:
[PluginInfo("Example plugin for testing")]
class Program
{
// ...
}
Raton calls a static Main method on your class. The signature is different — Raton injects two parameters:
null if invoked with no arguments.
public static Action<byte[]> _send;
static void Main(Action<byte[]> send, string[] args)
{
_send = send;
if (args == null || args.Length == 0)
{
ExecuteArg("default", "");
return;
}
ParseArgs(args);
}
Communication back to the client is done through the Pack class (from Stuff.dll). Fill key-value pairs, serialize with .Pacc(), and pass the resulting byte[] to _send.
Pack pack = new Pack();
pack.SetString("Packet", "UserMessage");
pack.SetString("Message", "Hello from my plugin!");
pack.SetString("Status", "Success");
_send(pack.Pacc());
Available Status values:
Arguments follow a -flag value convention. Iterate the args array, detect tokens starting with - as flag names, and accumulate subsequent tokens as the value. Use a StringBuilder for multi-word values.
static void ParseArgs(string[] args)
{
string currentArg = null;
StringBuilder valueBuilder = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
string arg = args[i];
if (arg.StartsWith("-"))
{
if (currentArg != null)
ExecuteArg(currentArg, valueBuilder.ToString());
currentArg = arg.ToLower();
valueBuilder.Clear();
}
else
{
if (valueBuilder.Length > 0) valueBuilder.Append(" ");
valueBuilder.Append(arg);
}
}
if (currentArg != null)
ExecuteArg(currentArg, valueBuilder.ToString());
}
"default" case in your switch. When invoked with no args, use it to print a usage hint.byte[] — pass result to _send()
Complete plugin. Responds to -msg <text> and prints a usage hint when called without arguments.
using Stuff;
using System;
using System.Text;
namespace TestPlugin
{
[PluginInfo("Example plugin for testing")]
class Program
{
public static Action<byte[]> _send;
static void Main(Action<byte[]> send, string[] args)
{
_send = send;
if (args == null || args.Length == 0)
{
ExecuteArg("default", "");
return;
}
ParseArgs(args);
}
static void ParseArgs(string[] args)
{
string currentArg = null;
StringBuilder valueBuilder = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
string arg = args[i];
if (arg.StartsWith("-"))
{
if (currentArg != null)
ExecuteArg(currentArg, valueBuilder.ToString());
currentArg = arg.ToLower();
valueBuilder.Clear();
}
else
{
if (valueBuilder.Length > 0) valueBuilder.Append(" ");
valueBuilder.Append(arg);
}
}
if (currentArg != null)
ExecuteArg(currentArg, valueBuilder.ToString());
}
static void ExecuteArg(string arg, string value)
{
switch (arg)
{
case "-msg":
Pack pack = new Pack();
pack.SetString("Packet", "UserMessage");
pack.SetString("Message", "Your message was: " + value);
pack.SetString("Status", "Success");
_send(pack.Pacc());
break;
default:
Pack pack2 = new Pack();
pack2.SetString("Packet", "UserMessage");
pack2.SetString("Message", "Try: -msg <text>");
pack2.SetString("Status", "Warning");
_send(pack2.Pacc());
break;
}
}
}
}
Raton may call your plugin with a null array when no input is given. Check args == null before everything.
The delegate is injected once in Main. Store it as a static field so all your methods can call it without passing it around.
Success, Warning, and Error map to colored log entries. Don't mark everything as Success.
One plugin = one purpose. Small, composable plugins are easier to debug and update. Or whatever.
Be careful when executing code from other people — this may cause RCE attempts or client stealing.