Skip to main content

Documentation 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.

By the end of this guide you will have a Terbin service listening on a named pipe, a client connecting to it, a [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.
1
Clone and Build
2
Clone the repository and open the solution in Visual Studio or your preferred IDE:
3
git clone https://github.com/FarlandsModdingTeam/TerbinProyect.git
cd TerbinProyect
dotnet build TerbinProyect.sln
4
The solution contains three projects: TerbinLibrary, TerbinService, and SimulateClient. All three must build successfully before you proceed.
5
Start the Service
6
Set TerbinService as the startup project and run it, or launch it from the command line:
7
dotnet run --project TerbinService
8
TerbinService uses the .NET Generic Host. Its entry point registers Worker as the single hosted service:
9
// TerbinService/Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

var host = builder.Build();
host.Run();
10
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:
11
// 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);
    };
}
12
Register Your Executables
13
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.
14
Here is the Stop handler that ships with Worker as a real example:
15
// 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);
}
16
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:
17
[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);
}
18
Connect a Client
19
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:
20
// 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;
}
21
Connect() calls ConnectAsync() on the underlying NamedPipeClientStream and then starts background send and receive loops.
22
Send a Packet
23
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:
24
// 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>());
25
IdArray accepts params byte[], so you can address multi-byte action keys that map to service/section combinations:
26
var action = new IdArray(
    (byte)CodeServices.ReadAll,
    (byte)CodeServicesSection.Plugin);

PacketRequest result = await communicator.Communicate(action, Array.Empty<byte>());
27
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.
28
Handle the Response
29
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:
30
// 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);
31
On the client side, the PacketRequest returned by Communicate() contains the full response:
32
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);
}
Only one server instance runs per pipe name by default. When a client connects to TerbinCommunicator(true, ...), the server’s background loop is fully occupied with that session. The OnNewClientConnect event exists precisely to address this: subscribe to it and call autoCreatePipe (or your equivalent) inside the handler so a new NamedPipeServerStream is opened and ready for the next client before the current session ends.
communicator.OnNewClientConnect += async () =>
{
    _ = Task.Run(() => autoCreatePipe(pTokenCancellation), pTokenCancellation);
};

Build docs developers (and LLMs) love