Command IO Reference
The Unturned™ dedicated server executes commands from console input and logs information to console output by default, using a built-in command input-output handler that writes to the terminal window and reads keystrokes from the keyboard buffer. This default implementation is sufficient for manual server management -- an operator typing commands directly into the server console and reading output from the same window -- but it becomes a bottleneck when the operator needs to integrate the server with external tooling, remote administration panels, or automated command dispatch systems.
The command IO system is extensible through the ICommandInputOutput interface. Any developer can replace the default console handler with a custom implementation that reads commands from a network socket, writes output to a database, or forwards keystrokes from a remote console application. The replacement mechanism is straightforward: implement the interface, get the CommandWindow singleton, and pass the instance to setIOHandler. The -NoDefaultConsole launch option suppresses the default console entirely when the custom handler provides its own input-output channels.
This article is the 57 Studios™ reference for the command IO system. It covers the default console behavior, the ICommandInputOutput interface contract, the CommandWindow singleton access pattern, the setIOHandler registration method, the -NoDefaultConsole launch option, and worked examples for three common custom IO scenarios: network socket command input, remote console forwarding, and dual-output logging.

The server console, shown above in its default configuration, is the primary interface through which operators send commands and read output. Custom IO handlers replace or extend this default interface to support remote management and automated tooling. A well-implemented custom handler integrates seamlessly with the server's command dispatch system and provides the operator with full control over command input and output routing without modifying the server's core command processing logic.
Documentation source: The command IO system documentation in this article is drawn from the official Smartly Dressed Games modding documentation.
Who this article is for
This article is written for Unturned™ dedicated server operators and plugin developers who want to customize how the server receives commands and logs output. Familiarity with C# interface implementation and the Unturned™ server plugin model (RocketMod or OpenMod) is assumed.
What you will learn
- How the default command IO handler reads console input and writes console output
- How to obtain the CommandWindow singleton through
Dedicator.commandWindow - How to implement the ICommandInputOutput interface
- How to register a custom IO handler with
CommandWindow.setIOHandler - How to suppress the default console with the
-NoDefaultConsolelaunch option - How to build a socket-based remote command dispatcher
- How to implement dual-output logging for audit trails
Background: the command IO architecture
The command IO system has two responsibilities: receiving commands from whatever source the operator uses and delivering output to whatever destination the operator needs. The default implementation ties both responsibilities to the server process's terminal window -- commands come from the keyboard and output goes to the terminal. This is the simplest possible arrangement and works well for a server operator who has direct terminal access.
The architecture is layered. At the bottom is the ICommandInputOutput interface, which defines the contract for sending output and receiving input. Above it is the CommandWindow class, which holds a reference to the current IO handler and exposes methods that the server's command processor calls to send output and check for pending input. At the top is the server's command dispatch system, which processes the commands that the IO handler provides.
The flowchart above shows the command flow through the IO architecture. Commands flow from the operator through the IO handler to the command dispatch system. Output flows back through the same path in reverse.
Default console behavior
The default IO handler uses the Windows console API (on Windows) or the Mono terminal interface (on Linux) to interact with the server process's terminal window. It provides:
| Feature | Description |
|---|---|
| Command input | Reads keystrokes from the keyboard buffer asynchronously |
| Command history | Maintains a history of previously entered commands accessible with the arrow keys |
| Tab completion | Completes partial command names when the Tab key is pressed |
| Output display | Writes log messages and command output to the terminal window in real time |
| Color output | Uses terminal colors for log message severity levels (errors in red, warnings in yellow) |
The default handler is suitable for operators who have direct terminal access through SSH, RDP, or a local session. It is not suitable for operators who need to send commands from a remote dashboard, integrate the server with an automated orchestration system, or maintain a searchable audit trail of all commands and output.
The ICommandInputOutput interface
The ICommandInputOutput interface is the contract that custom IO handlers must implement. It defines the methods that the CommandWindow calls to send output and check for input.
The interface is small, consisting of:
| Method | Purpose |
|---|---|
initialize(CommandWindow) | Called when the handler is registered. The handler receives a reference to the CommandWindow singleton. |
shutdown(CommandWindow) | Called when the server is shutting down. The handler should release any resources held by the custom IO implementation. |
updateInput() | Called on each frame to check for pending input. The handler should read any available input from its source and pass it to the CommandWindow using commandWindow.allowInput or equivalent. |
outputMessage(string message, string color) | Called when the server produces output. The handler should forward the message to the configured output destination. |
The interface does not define how commands are received -- that is the handler's responsibility. A handler that reads from a network socket implements updateInput to check the socket for pending data, parse the received text into command strings, and submit them to the command processor. A handler that writes to a file implements outputMessage to append each message to the log file.
Obtaining the CommandWindow singleton
The CommandWindow singleton is accessible through Dedicator.commandWindow. This is the static property that provides access to the current command IO infrastructure:
csharp
CommandWindow commandWindow = Dedicator.commandWindow;The singleton is available after the server has initialized the command IO system, which occurs during the server startup sequence before the map loads or any plugins are initialized. A custom IO handler should be registered as early as possible in the plugin lifecycle -- typically in the plugin's OnLoad or initialization method -- so that no commands or output are processed through the default handler before the custom handler is installed.
Registering a custom IO handler
Registering a custom IO handler is a three-step process:
- Create a class that implements
ICommandInputOutput. - Obtain the
CommandWindowsingleton fromDedicator.commandWindow. - Call
CommandWindow.setIOHandler(myHandlerInstance).
The registration method replaces the current IO handler with the new one. After registration, all command input and output routes through the custom handler. The previous handler is discarded.
csharp
public class SocketCommandHandler : ICommandInputOutput
{
public void initialize(CommandWindow commandWindow)
{
// Initialize network socket, file handle, or other resource
}
public void shutdown(CommandWindow commandWindow)
{
// Release resources, close sockets, flush buffers
}
public void updateInput()
{
// Read pending input from the custom source
}
public void outputMessage(string message, string color)
{
// Forward the message to the custom destination
}
}
// Registration:
CommandWindow commandWindow = Dedicator.commandWindow;
commandWindow.setIOHandler(new SocketCommandHandler());The handler is registered once and remains active until the server shuts down or another handler is registered to replace it.
The -NoDefaultConsole launch option
The -NoDefaultConsole launch option suppresses the default console window entirely. When this option is active, the server does not display the default terminal window, does not read keystrokes from the keyboard buffer, and does not write output to the terminal. All command input and output must be handled by a custom IO handler.
This option is useful for:
- Servers running as Windows services or Linux daemons where no interactive console is needed
- Servers managed exclusively through remote administration panels that provide their own command interface
- Servers where the console window is undesirable (e.g., servers running on locked-down hosting environments)
Without a custom IO handler registered, the -NoDefaultConsole option leaves the server with no way to receive commands or display output. The option should only be used when a custom IO handler is installed and confirmed working.
Worked example: socket-based remote command handler
The most common custom IO scenario is a socket-based handler that allows an external tool to send commands to the server and receive output over a TCP connection. This pattern is used by remote administration panels, Discord bot command relays, and automated server management scripts.
csharp
public class TcpCommandHandler : ICommandInputOutput
{
private TcpListener listener;
private TcpClient client;
private NetworkStream stream;
private CommandWindow commandWindow;
public void initialize(CommandWindow cmdWindow)
{
this.commandWindow = cmdWindow;
this.listener = new TcpListener(IPAddress.Any, 27016);
this.listener.Start();
this.listener.BeginAcceptTcpClient(OnClientConnected, null);
}
private void OnClientConnected(IAsyncResult result)
{
this.client = this.listener.EndAcceptTcpClient(result);
this.stream = this.client.GetStream();
}
public void shutdown(CommandWindow cmdWindow)
{
stream?.Close();
client?.Close();
listener?.Stop();
}
public void updateInput()
{
if (stream != null && stream.DataAvailable)
{
byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
string command = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim();
if (!string.IsNullOrEmpty(command))
{
commandWindow.allowInput(command);
}
}
}
public void outputMessage(string message, string color)
{
if (stream != null && stream.CanWrite)
{
byte[] data = Encoding.UTF8.GetBytes(message + "\n");
stream.Write(data, 0, data.Length);
}
}
}The handler opens a TCP listener on port 27016, accepts one client connection, reads commands from the socket, and writes output back to the socket. The updateInput method is called on every frame and checks the socket for available data.
Worked example: dual-output logging handler
A dual-output handler writes command output to both the default terminal and a file, providing an audit trail without losing the interactive console experience.
csharp
public class DualOutputHandler : ICommandInputOutput
{
private ICommandInputOutput defaultHandler;
private StreamWriter logWriter;
public void initialize(CommandWindow cmdWindow)
{
this.defaultHandler = cmdWindow; // Store reference to default
this.logWriter = new StreamWriter("command-audit.log", append: true);
}
public void shutdown(CommandWindow cmdWindow)
{
logWriter?.Flush();
logWriter?.Close();
}
public void updateInput()
{
// Delegate input handling to default
defaultHandler.updateInput();
}
public void outputMessage(string message, string color)
{
// Write to both destinations
defaultHandler.outputMessage(message, color);
logWriter.WriteLine($"[{DateTime.UtcNow:O}] {message}");
logWriter.Flush();
}
}The dual-output handler wraps the default handler, forwarding output to both the terminal and a timestamped log file.
Diagnostic table
| Symptom | Most likely cause | Resolution |
|---|---|---|
| Custom handler registered but no commands are processed | updateInput is not calling commandWindow.allowInput or equivalent | Verify the handler reads input and submits it through the command window |
| Output appears in the default console after custom handler registration | The handler's outputMessage does not override the default output path | Confirm setIOHandler was called successfully and that the handler's output method is implemented |
Server crashes on startup with -NoDefaultConsole and no custom handler | No IO handler is available to receive commands | Register a custom handler before using -NoDefaultConsole, or remove the launch option |
| Socket handler accepts connection but commands are not executed | The command string includes a trailing newline or carriage return that the parser rejects | Trim whitespace from the command string before submitting it to allowInput |
| Log file grows indefinitely | The dual-output handler has no log rotation | Implement log rotation based on file size or date in the outputMessage method |
| Commands processed multiple times | updateInput re-reads commands that were already read | Track the last-read position in the input buffer and only read new data |
Frequently asked questions
Can I register multiple IO handlers simultaneously?
No. The CommandWindow supports exactly one IO handler at a time. Registering a new handler replaces the previous one. If you need to send output to multiple destinations, implement a composite handler that forwards output to each destination internally.
Does the ICommandInputOutput interface exist in the vanilla Unturned assembly or does it require a plugin framework?
The interface is part of the vanilla Unturned assembly (SDG.Unturned namespace). It does not require RocketMod, OpenMod, or any other plugin framework. Any C# code that runs in the Unturned process context can implement and register a custom IO handler.
Can I use a custom IO handler without a plugin?
Custom IO handlers are typically registered from a plugin's initialization code, but they can also be registered from any code that runs in the Unturned server process. The handler must be registered before any commands need to be processed.
Is the -NoDefaultConsole option available on both Windows and Linux?
Yes. The option works on both platforms. On Windows, it suppresses the console window creation. On Linux, it prevents the default Mono terminal handler from initializing.
Does the custom IO handler persist across server restarts?
The handler is registered in memory and is lost when the server process exits. Each server startup requires a fresh registration. If you use a plugin to register the handler, the plugin's initialization code runs on each startup and re-registers the handler automatically.
Can I use a custom IO handler to intercept commands before they are executed?
The handler receives commands through updateInput and submits them through the command window. The handler can inspect, modify, or reject commands before submission. If the handler does not submit a command, the command is not executed.
Is the CommandWindow singleton available during plugin initialization?
The CommandWindow singleton is initialized early in the server startup sequence, before plugins are loaded. It is safe to access Dedicator.commandWindow from a plugin's OnLoad or initialization method.
How do I handle multiple simultaneous connections in a socket-based handler?
The reference implementation accepts one client connection. For multiple simultaneous connections, maintain a list of connected clients in the handler and broadcast outputMessage to all of them. The updateInput method should cycle through connected clients and read pending data from each.
Can I redirect command output to a database?
Yes. Implement outputMessage to insert the message into a database table instead of writing to a file or terminal. The handler receives the message string and can parse it into structured fields before insertion.
How do I test a custom IO handler without deploying it to a production server?
Create a test server instance with the same assembly references as the production server. Register the handler from a test plugin or a test script. Manually trigger command output by executing known commands through the custom handler's input path. Verify that output appears at the expected destination with the expected formatting. Test the shutdown path by terminating the server process and confirming that the handler's shutdown method runs and releases resources.
Can a custom IO handler be used to implement a command whitelist or blacklist?
Yes. The handler inspects every command before submitting it to the command window. If the command is not in the whitelist or is in the blacklist, the handler discards it without submitting it. The operator sees no feedback for a discarded command -- the command simply does not execute. For user-facing feedback, the handler should call outputMessage to notify the operator that the command was rejected.
Does the ICommandInputOutput interface support asynchronous input reading?
The interface methods are synchronous, but the implementation can use asynchronous operations internally. The updateInput method is called on the main thread and should not block. Asynchronous socket or file operations should be performed on background threads, and the results should be queued for consumption by updateInput on the main thread.
Can I reuse the same custom IO handler class for multiple servers?
The handler class is a plain C# class with no server-specific state. It can be instantiated once per server process. Each server process has its own CommandWindow singleton, and each process must register its own handler instance.
How do I format output messages with the correct severity color?
The color parameter in outputMessage accepts a string that specifies the output color. The valid color values match the console color names used by the server's default output formatter: "red" for errors, "yellow" for warnings, "green" for success messages, and "white" or null for standard informational output. The handler can use the color value to apply formatting in the output destination -- for example, using red text in a terminal or a red-colored embed in a Discord relay.
What happens to output messages that are generated before the custom handler is registered?
Output messages generated before the handler is registered are processed by the default handler. If a custom handler is registered after the server has started and output has already been generated, that prior output is not re-sent through the custom handler.
Default command processing pipeline
Understanding the default command processing pipeline helps operators and plugin developers design custom IO handlers that integrate correctly with the server's command infrastructure.
Startup command processing
When the server starts, it reads the Commands.dat file from the server configuration directory. Each line in Commands.dat is treated as a command that the server executes during the startup sequence. These commands are processed through the command IO system -- the default handler reads them from the file directly, while a custom handler would receive them through whatever mechanism it implements.
The startup command processing order is:
- The server initializes the command IO system and registers the default handler.
- The server reads
Commands.dat, parsing each line as a separate command. - Each command is dispatched through the command processor, which executes it against the server state.
- Output from each command is sent through the IO handler's
outputMessagemethod. - After all
Commands.datcommands are processed, the server proceeds with map loading and plugin initialization.
A custom IO handler that is registered during plugin initialization runs after the Commands.dat commands have already been processed. If the handler needs to capture output from those commands, it must be registered before they execute, which requires modifying the server startup sequence.
Runtime command processing
During normal operation, the command IO system operates on a frame-by-frame basis. On each frame:
- The IO handler's
updateInputmethod is called. - The handler checks its input source for pending commands.
- If a command is available, the handler submits it to the
CommandWindowthroughallowInputor the equivalent method. - The command processor executes the command and generates output.
- The output is passed to the handler's
outputMessagemethod.
This per-frame cycle means there is a small delay between a command being available at the input source and the command being executed. The delay is typically less than one frame (approximately 16-33 milliseconds at 30-60 frames per second).
Implementing a webhook-based command handler
A webhook-based handler extends the socket pattern to accept commands through HTTP POST requests instead of a raw TCP connection. This pattern is compatible with Discord bot integrations, web dashboards, and REST API consumers.
csharp
public class WebhookCommandHandler : ICommandInputOutput
{
private HttpListener listener;
private CommandWindow commandWindow;
private ConcurrentQueue<string> commandQueue;
public void initialize(CommandWindow cmdWindow)
{
this.commandWindow = cmdWindow;
this.commandQueue = new ConcurrentQueue<string>();
this.listener = new HttpListener();
this.listener.Prefixes.Add("http://*:27017/");
this.listener.Start();
// Accept requests on a background thread
Task.Run(() => AcceptRequests());
}
private async Task AcceptRequests()
{
while (listener.IsListening)
{
var context = await listener.GetContextAsync();
using (var reader = new StreamReader(context.Request.InputStream))
{
string command = await reader.ReadToEndAsync();
commandQueue.Enqueue(command.Trim());
}
byte[] response = Encoding.UTF8.GetBytes("OK");
context.Response.OutputStream.Write(response, 0, response.Length);
context.Response.Close();
}
}
public void updateInput()
{
if (commandQueue.TryDequeue(out string command))
{
commandWindow.allowInput(command);
}
}
public void outputMessage(string message, string color)
{
// Forward to webhook subscribers or discard
}
public void shutdown(CommandWindow cmdWindow)
{
listener?.Stop();
}
}The webhook handler listens on port 27017 for HTTP POST requests, queues received commands, and submits them from the updateInput method on the main thread.
Default command processing pipeline
- Register the custom IO handler as early as possible in the plugin lifecycle to capture all output from the startup sequence.
- Implement proper resource cleanup in the
shutdownmethod. Open sockets, file handles, and database connections should be closed gracefully. - Use the
-NoDefaultConsoleoption only after confirming that the custom handler is working correctly. A failed handler with no default console leaves the server unmanageable. - Include error handling in
outputMessage. A failed write to the custom destination should not crash the server or block the command processing loop. - Add thread safety to handlers that accept multiple connections. The
updateInputandoutputMessagemethods may be called from different threads. - Log the registration event so that the server records when the custom handler was installed and by which plugin.
Appendix A: ICommandInputOutput interface reference
| Method | Signature | Called when | Purpose |
|---|---|---|---|
initialize | void initialize(CommandWindow) | Handler registration | Set up resources, establish connections |
shutdown | void shutdown(CommandWindow) | Server shutdown | Release resources, close connections |
updateInput | void updateInput() | Every frame | Read and submit pending commands |
outputMessage | void outputMessage(string, string) | Every output event | Forward message to destination |
Appendix B: Command IO handler registration code
csharp
// Get the CommandWindow singleton
CommandWindow cmdWindow = Dedicator.commandWindow;
// Create and register a custom handler
cmdWindow.setIOHandler(new MyCustomHandler());Appendix C: Command IO handler comparison table
| Handler type | Input source | Output destination | Use case | Complexity |
|---|---|---|---|---|
| Default terminal | Keyboard buffer | Terminal window | Direct operator access | None |
| TCP socket | Network socket connection | Same socket | Remote administration panel | Medium |
| Webhook HTTP | HTTP POST requests | HTTP response | REST API consumers | Medium |
| Dual-output | Keyboard buffer (via default) | Terminal + file | Audit trail + interactive | Low |
| Database | Plugin-managed | Database table | Persistent audit log | Medium |
| Discord relay | Discord bot command | Discord channel | Community management | High |
Appendix D: Common ICommandInputOutput implementation errors
| Error | Symptom | Correction |
|---|---|---|
updateInput blocks the main thread | Server freezes or stutters | Use async I/O on background threads; updateInput should only dequeue ready input |
outputMessage throws an exception | Server logs show unhandled exceptions in output | Wrap the output logic in a try-catch block; log the error internally without rethrowing |
shutdown is never called | Resources are leaked on server exit | Confirm the server process exits cleanly; the shutdown method is called during normal shutdown but may not be called on forced termination |
| Handler registered too late | Startup Commands.dat output is lost | Register the handler from the plugin's constructor or OnLoad method, not from a delayed initialization |
| Socket handler accepts only one connection | Subsequent connections are ignored | Maintain a list of connected clients and cycle through them in updateInput and outputMessage |
Appendix E: External references
- Smartly Dressed Games modding documentation -- official reference for
ICommandInputOutput,CommandWindow, andDedicator.commandWindow. - Debugging Server Exceptions -- the next article; covers server-side exception analysis.
- Server Config Files: Commands.dat, Players.dat, Config.json -- the server configuration reference; commands executed automatically through
Commands.datinterface with the command IO system.
Document history
| Version | Date | Author | Notes |
|---|---|---|---|
| 1.0 | 2026-07-26 | 57 Studios | Initial publication. Complete reference for the command IO system, ICommandInputOutput interface, custom handler registration, -NoDefaultConsole option, socket and dual-output worked examples, and diagnostic guidance. |
Authoring checklist
- [ ] Custom IO handler implements all four ICommandInputOutput methods
- [ ] Handler is registered before it is needed (early in plugin lifecycle)
- [ ]
-NoDefaultConsoleis only enabled after handler verification - [ ] Resource cleanup is implemented in the shutdown method
- [ ] Multiple simultaneous connections are handled if the scenario requires them
- [ ] Error handling is included in outputMessage
- [ ] Handler registration is logged for audit purposes
Cross-references
- Debugging Server Exceptions -- the next article in this series; covers exception analysis in the server process.
- Server Update Notifications -- the previous article; covers update notification channels that interface with the command IO system.
- Server Config Files: Commands.dat, Players.dat, Config.json --
Commands.datis processed by the command IO system at startup. - Smartly Dressed Games modding documentation -- official reference for the command IO system.
- Unturned on Steam -- Unturned™ store page.
