By the end of this guide you will have a Terbin service listening on a named pipe, a client connecting to it, aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/FarlandsModdingTeam/TerbinProyect/llms.txt
Use this file to discover all available pages before exploring further.
[TerbinExecutable]-decorated handler running on the server, and a round-trip packet exchange you can inspect in the console. No external dependencies are required beyond the .NET SDK.
git clone https://github.com/FarlandsModdingTeam/TerbinProyect.git
cd TerbinProyect
dotnet build TerbinProyect.sln
The solution contains three projects:
TerbinLibrary, TerbinService, and SimulateClient. All three must build successfully before you proceed.TerbinService uses the .NET Generic Host. Its entry point registers Worker as the single hosted service:// TerbinService/Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
host.Run();
Worker.ExecuteAsync immediately creates a TerbinCommunicator in server mode (pIsServer: true), which opens a NamedPipeServerStream named "TerbinPipe" and begins waiting for a client connection. The OnNewClientConnect callback ensures a fresh communicator is prepared for every new client that connects:// TerbinService/Worker.cs
protected override async Task ExecuteAsync(CancellationToken pStoppingToken)
{
Cts = CancellationTokenSource.CreateLinkedTokenSource(pStoppingToken);
TerbinExecutor.Register(Assembly.GetExecutingAssembly());
await autoCreatePipe(Cts.Token);
}
private async Task autoCreatePipe(CancellationToken pTokenCancellation)
{
var communicator = new TerbinCommunicator(true, pTokenCancellation);
communicator.OnRecive += async (pCapsule) =>
{
CurrentContext.Value = new InfoLocalThreads { Communicator = communicator };
return await TerbinExecutableManager.DispatchAsync(
pCapsule.Head, pCapsule.Payload, pCapsule.ActionMethod);
};
communicator.OnNewClientConnect += async () =>
{
_ = Task.Run(() => autoCreatePipe(pTokenCancellation), pTokenCancellation);
};
}
TerbinExecutor.Register(Assembly.GetExecutingAssembly()) scans every public and non-public static method in the assembly, finds those decorated with [TerbinExecutable], validates that the signature matches Task<InfoResponse?>(Header, byte[], CancellationToken), and registers them with TerbinExecutableManager.// TerbinService/Worker.cs
[TerbinExecutable((byte)CodeTerbinProtocol.Stop)]
public static async Task<InfoResponse?> Stop(
Header pHead, byte[] pParameters, CancellationToken pToken)
{
_ = Task.Run(async () =>
{
await Task.Delay(100);
Console.WriteLine("[Worker] Execution stoped");
if (pToken.IsCancellationRequested) return;
_appLifetime?.StopApplication();
Cts?.Cancel();
});
Console.WriteLine("[Worker] Stopping execution...");
return InfoResponse.CreateSucces(pHead.IdRequest);
}
The attribute accepts one or more
byte values that form the action key. CodeTerbinProtocol.Stop resolves to 0. To add your own handler, follow the same pattern:[TerbinExecutable((byte)CodeServices.Execute, (byte)CodeServicesSection.Plugin)]
public static async Task<InfoResponse?> InstallPlugin(
Header pHead, byte[] pParameters, CancellationToken pToken)
{
// Deserialize pParameters, call your manager, return a response.
return InfoResponse.CreateSucces(pHead.IdRequest);
}
In
SimulateClient, a TerbinCommunicator is created in client mode (pIsServer: false). Its own executables are registered, an OnRecive handler is wired up for incoming server-initiated packets, and Connect() performs the pipe handshake:// SimulateClient/Program.cs
var communicator = new TerbinCommunicator(false);
TerbinExecutor.Register(Assembly.GetExecutingAssembly());
communicator.OnRecive += async p =>
{
return await TerbinExecutableManager.DispatchAsync(
p.Head, p.Payload, p.ActionMethod);
};
if (await communicator.Connect())
{
Console.WriteLine("[Client] ¡Conectado!");
}
else
{
Console.WriteLine("[Client] ¡Error de Conexion!");
return;
}
Connect() calls ConnectAsync() on the underlying NamedPipeClientStream and then starts background send and receive loops.Use
Communicate() to send a packet and await the response, or Send() for a fire-and-forget message. Both accept an IdArray action key and a byte[] payload:// Ask the server to stop — action byte 0 = CodeTerbinProtocol.Stop
var stopAction = new IdArray((byte)CodeTerbinProtocol.Stop);
PacketRequest response = await communicator.Communicate(stopAction, Array.Empty<byte>());
// Fire-and-forget: send without waiting for a reply
await communicator.Send(
new IdArray((byte)CodeServices.Info),
Array.Empty<byte>());
IdArray accepts params byte[], so you can address multi-byte action keys that map to service/section combinations:var action = new IdArray(
(byte)CodeServices.ReadAll,
(byte)CodeServicesSection.Plugin);
PacketRequest result = await communicator.Communicate(action, Array.Empty<byte>());
Communicate() registers a TaskCompletionSource<PacketRequest> keyed on the auto-generated ushort request ID and blocks the caller until the matching response packet arrives. The wait is bounded by communicator.MaximumResponseTime seconds (default 8, sourced from TerbinProtocol.MAXIMUS_RESPONSE_TIME). If the deadline passes, the returned PacketRequest has Head.Status == CodeStatus.OverMaximumTime.Send() enqueues the packet and returns immediately — use it for notifications or progress updates where you do not need a reply.On the server side, every handler receives a
Header, the deserialized byte[] payload, and a CancellationToken. It must return Task<InfoResponse?>. Use the static factory methods on InfoResponse to build your reply:// Signal success with no payload
return InfoResponse.CreateSucces(pHead.IdRequest);
// Signal success and attach a byte[] result
return InfoResponse.CreateSucces(pHead.IdRequest, resultBytes);
// Signal a known error status
return InfoResponse.Create(pHead.IdRequest, CodeStatus.NotFound);
// Signal an internal worker error
return InfoResponse.CreateInteralError(pHead.IdRequest);
PacketRequest response = await communicator.Communicate(action, payload);
if (response.Head.Status == CodeStatus.Succes)
{
// response.Payload contains the server's returned bytes
Console.WriteLine("Success! Payload length: " + response.Payload.Length);
}
else
{
Console.WriteLine("Error: " + response.Head.Status);
}