Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Server

The RPFM Server (rpfm_server) is a standalone backend process that exposes a WebSocket-based IPC protocol for programmatic access to RPFM’s functionality. Any language that supports WebSockets and JSON can communicate with the server — no Rust or Qt dependencies required on the client side.

This section documents the protocol, all available commands and responses, and provides client implementation examples.

Connecting

The server listens on ws://127.0.0.1:45127/ws by default. Connect with any WebSocket client:

const ws = new WebSocket("ws://127.0.0.1:45127/ws");
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri("ws://127.0.0.1:45127/ws"), CancellationToken.None);

Upon connection, the server immediately sends a SessionConnected message (with id: 0) containing the session ID assigned to this connection:

{ "id": 0, "data": { "SessionConnected": 42 } }

Sessions

The server supports multiple concurrent sessions. Each session maintains its own state (open packs, game selection, etc.).

Reconnection

To reconnect to an existing session, append ?session_id=<id> to the WebSocket URL:

const ws = new WebSocket("ws://127.0.0.1:45127/ws?session_id=42");
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri("ws://127.0.0.1:45127/ws?session_id=42"), CancellationToken.None);

Session Listing (REST)

A REST endpoint is available for listing active sessions:

GET http://127.0.0.1:45127/sessions

Returns a JSON array of session info objects:

[
  {
    "session_id": 42,
    "connection_count": 1,
    "timeout_remaining_secs": null,
    "is_shutting_down": false,
    "pack_names": ["my_mod.pack"]
  }
]

Disconnection

Send the ClientDisconnecting command before closing the WebSocket so the server can clean up resources immediately instead of waiting for a timeout:

{ "id": 99, "data": "ClientDisconnecting" }

Message Format

Every message — both commands (client to server) and responses (server to client) — is wrapped in a Message envelope:

{
  "id": <number>,
  "data": <Command or Response>
}
FieldTypeDescription
idnumberUnique request ID. The server echoes this in the response so the client can correlate requests and responses. Use 0 only for unsolicited server messages.
dataobject or stringThe command or response payload.

Request-Response Correlation

The id field enables asynchronous communication. Multiple requests can be in flight simultaneously — match each response back to its request by id.

Serialization Convention

All messages use JSON. Rust enums (which back both Command and Response) are serialized by serde as follows:

Rust VariantJSON SerializationExample
Unit variant"VariantName""NewPack"
Newtype variant{ "VariantName": value }{ "ClosePack": "my_mod.pack" }
Tuple variant{ "VariantName": [v1, v2, ...] }{ "SavePackAs": ["key", "/path/to/file"] }

Quick Start

Here is a minimal example that connects to the server, opens a pack file, and prints the response:

const ws = new WebSocket("ws://127.0.0.1:45127/ws");

let nextId = 1;
let currentSessionId: number | null = null;

function send(command: object | string): number {
  const id = nextId++;
  ws.send(JSON.stringify({ id, data: command }));
  return id;
}

// Listen for responses
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  // Handle the SessionConnected message sent immediately after connection
  if (typeof msg.data === "object" && "SessionConnected" in msg.data) {
    currentSessionId = msg.data.SessionConnected;
    console.log(`Connected to session ${currentSessionId}`);
    return;
  }

  console.log(`Response for request ${msg.id}:`, msg.data);
};

// Open a pack file once connected
ws.onopen = () => {
  send({ OpenPackFiles: ["/path/to/my_mod.pack"] });
};
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri("ws://127.0.0.1:45127/ws"), CancellationToken.None);

int nextId = 1;
int? currentSessionId = null;

async Task Send(JsonElement command)
{
    var id = nextId++;
    var msg = JsonSerializer.Serialize(new { id, data = command });
    var bytes = Encoding.UTF8.GetBytes(msg);
    await ws.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
}

// Listen for responses
var buffer = new byte[4096];
while (ws.State == WebSocketState.Open)
{
    var result = await ws.ReceiveAsync(buffer, CancellationToken.None);
    var json = Encoding.UTF8.GetString(buffer, 0, result.Count);
    var msg = JsonDocument.Parse(json).RootElement;

    // Handle the SessionConnected message sent immediately after connection
    if (msg.GetProperty("data").ValueKind == JsonValueKind.Object
        && msg.GetProperty("data").TryGetProperty("SessionConnected", out var sessionId))
    {
        currentSessionId = sessionId.GetInt32();
        Console.WriteLine($"Connected to session {currentSessionId}");

        // Open a pack file once connected
        var openCmd = JsonSerializer.SerializeToElement(
            new { OpenPackFiles = new[] { "/path/to/my_mod.pack" } });
        await Send(openCmd);
        continue;
    }

    Console.WriteLine($"Response for request {msg.GetProperty("id")}: {msg.GetProperty("data")}");
}