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. DOWNLOAD THE REPOSITORY HERE TO START
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 folderYour 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 (we'll get to that soon)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 null when no input is given. Use default: as we said before
The delegate is injected once in Main. Store it as a static field so all your methods can call it without passing it around.
Information, 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.