Thanks to visit codestin.com
Credit goes to github.com

Skip to content

A cross-platform Terraria server launcher built on UnifiedServerProcess, with multi-world support, per-instance consoles, and hot-reload plugin architecture.

License

Notifications You must be signed in to change notification settings

CedaryCat/UnifierTSL

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UnifierTSL

Languages: English | 简体中文

An experiment-friendly Terraria server launcher built on OTAPI USP, bundling per-instance consoles, early publishing helpers, and plugin scaffolding.

.NET 9.0 License: GPL-3.0 Platforms


Overview

UnifierTSL aims to wrap OTAPI's Unified Server Process in a friendlier workflow so you can explore hosting multiple Terraria worlds without juggling ports or fragile scripts. The launcher tries to keep lifecycles in sync, route players automatically, and spin up a dedicated console client per world so inputs stay separated.

The solution currently bundles the launcher, publisher, console client, and sample plugins in one place. Shared services live in UnifiedServerCoordinator, event traffic flows through UnifierApi.EventHub, and PluginHost.PluginOrchestrator works toward hot-swappable integrations without touching the core launcher.

Quick Glance

  • Supports running multiple Terraria worlds from a single host process with per-instance console windows.
  • Includes a lightweight publisher for producing repeatable bundles with sample plugins and configs.
  • Provides evolving plugin tooling with hot reload, dependency staging, and metadata helpers.
  • Targets Windows, Linux (x64 and ARM), and macOS via the .NET 9.0 toolchain.

Tip: Skim the Quick Start section if you want to launch a world in the next five minutes.

Table of Contents

Core Capabilities

  • Multi-world focus: Experiment with hosting multiple isolated Terraria worlds in one process, each with tracked state and resource notes.
  • Adjustable control room: Add or retire server contexts while players are connected, aiming to keep everyone on the same listening port.
  • Plugin support: Load .NET plugins from a structured plugins/ tree with metadata, JSON/TOML configs, and hot-reload orchestration that is still evolving.
  • Managed module loading: Collectible ModuleLoadContext instances aim to deliver hot reloading, dependency sharing, automatic NuGet acquisition, and platform-aware native resolution.
  • Shared logging: UnifierApi.LogCore exposes pluggable filters, writers, and metadata injectors so diagnostics can be rerouted or enriched without restarts.
  • Bundled TShock build: Includes the USP-adapted TShock 5.2.2 build so familiar permissions, REST endpoints, SSC, and command tooling are close at hand.
  • Dedicated consoles: Spawns a console client per server context through named pipes so each world's input and colored output stay readable.
  • Publishing workflow: Helps generate repeatable bundles with RID-specific assets, included plugins, and configuration defaults for consistent deployments.
  • Cross-platform targets: Can publish for win-x64, linux-x64, linux-arm64, linux-arm, and osx-x64 by leaning on the .NET SDK pipeline.
  • High-performance fundamentals: Inherits USP's structural optimizations including IL-transformed Tile types (interface-to-struct conversion), ref/in parameter propagation in Collision, and value-type packet protocols for efficient multi-world coordination.

Under the Hood

Curious what powers the launcher? Here is the stack at a glance so you know what you are installing and why it matters.

  • Runtime: .NET 9.0 targeting framework-dependent executables with RID-specific assets generated by the publisher.

  • USP Core: Based on OTAPI.UnifiedServerProcess 1.0.13 to transform the Terraria dedicated server into a unified multi-world host.

  • Key Packages:

    Package Version Purpose
    OTAPI.USP 1.0.13 Unified Server Process core
    ModFramework 1.1.15 IL modification framework used during patching
    MonoMod.RuntimeDetour 25.2.3 Runtime method hooking and detouring
    Tomlyn 0.19.0 TOML configuration parsing for launcher and plugins
    linq2db 5.4.1 Database abstraction layer leveraged by bundled plugins
    Microsoft.Data.Sqlite 9.0.0 SQLite provider used by TShock and sample plugins

Heads-up: Keep an eye on the OTAPI and TShock release notes when you upgrade packages so launcher hooks, configs, and plugins keep working together.

How It Fits Together

Picture UnifierTSL as a coordination layer that keeps several specialist subsystems in sync:

  • UnifiedServerCoordinator owns client sockets, server contexts, and packet routing across hosted worlds (src/UnifierTSL/UnifiedServerCoordinator.cs).
  • UnifierApi exposes lifecycle hooks, coordinates argument parsing, and instantiates shared infrastructure such as EventHub and the plugin orchestrator (src/UnifierTSL/UnifierApi.Internal.cs).
  • ServerContext models each hosted Terraria world with isolated state so transfers and teardown can occur without cross-contamination (src/UnifierTSL/Servers/).
  • PluginHost.PluginOrchestrator orchestrates multiple IPluginHost implementations to load plugins, register event handlers, and enable hot-reload without requiring changes to the launcher entry point (src/UnifierTSL/PluginHost/).
  • Utilities.CLI parses raw arguments and nested key:value pairs for both the launcher and publisher (src/UnifierTSL/Utilities.cs).
  • Logging joins Logger, RoleLogger, and metadata injectors under one hot-swappable pipeline (src/UnifierTSL/Logging/), letting subsystem roles clone loggers while reusing or overriding the shared UnifierApi.LogCore.
  • Network patches located under src/UnifierTSL/Network/ bridge OTAPI primitives with the coordinator's expectations to keep USP packets flowing safely.

Want the big picture flow? Start with Program.cs in each project, then trace into UnifiedServerCoordinator to see how instances come online.

Initialization Flow

Here is the high-level startup order once the launcher kicks off:

  1. Initialize assembly resolution, localization resources, and USP network patches during launcher boot.
  2. Bring up global systems such as UnifiedServerCoordinator, logging, and shared infrastructure while wiring the event hub.
  3. Load plugins in ascending InitializationOrder, allowing each dependency to observe prior initialization results.
  4. Launch configured servers, begin listening for clients on the unified port, and hand off console duties to isolated client processes.

Quick Start

Prerequisite: Confirm the .NET 9.0 SDK is installed (dotnet --list-sdks); install or upgrade from https://dotnet.microsoft.com/ if the command is missing or reports an older major version.

Pick Your Setup

  • Pick Use a Release Bundle if you want a prebuilt package with TShock, sample plugins, and launch scripts ready to copy onto a server.
  • Pick Run from Source when you need deep debugging for plugin work, are tweaking the launcher, or wiring everything into CI/CD; plugin authors can also stay outside the repo by targeting the published NuGet packages.

Command contexts: release bundle commands run from the extracted bundle directory, while source workflow commands assume the repository root.

Use a Release Bundle

  1. Download the archive that matches your platform from the Releases tab: utsl-<rid>-v<version>.zip (Windows) or .tar.gz (Linux/macOS).
  2. Extract the archive on the host. Expect directories such as lib/, plugins/, config/, app/, and the entry point (UnifierTSL.exe for Windows or UnifierTSL elsewhere).
  3. Launch the server with flags that describe each hosted world:
    • Windows (PowerShell)
      .\UnifierTSL.exe -lang 7 -port 7777 -password changeme `
        -server "name:S1 worldname:S1 gamemode:3 size:1 evil:0 seed:\"for the worthy\"" `
        -server "name:S2 worldname:S2 gamemode:2 size:2"
    • Linux or macOS
      chmod +x UnifierTSL
      ./UnifierTSL -lang 7 -port 7777 -password changeme \
        -server "name:S1 worldname:S1 gamemode:3 size:1 evil:0 seed:\"for the worthy\"" \
        -joinserver first
  4. Drop any custom plugins or configs into the extracted plugins/ and config/ directories, then restart the launcher to pick them up.

Release availability: If the Releases tab is empty, pull the latest GitHub Actions artifact or build your own bundle using the publisher recipe below.

Run from Source

Use this workflow when you need to inspect, modify, or automate the launcher.

  1. Clone and restore (skip if you already have the repo)
    git clone https://github.com/CedaryCat/UnifierTSL.git
    cd UnifierTSL
    dotnet restore src/UnifierTSL.sln
  2. Publish a bundle (recommended for operators)
    dotnet run --project src/UnifierTSL.Publisher/UnifierTSL.Publisher.csproj -- \
      --rid win-x64 \
      --excluded-plugins ExamplePlugin
    • Add -c Release for production builds.
    • Replace win-x64 with the RID you target (such as linux-x64 or osx-x64).
    • Output lands in src/UnifierTSL.Publisher/bin/<Configuration>/net9.0/utsl-<RID>.zip, a zipped copy of the publisher output.
    • Prefer the RID for the platform running the publisher so the generated AppHost matches the local .NET runtime.
  3. Smoke test locally
    dotnet run --project src/UnifierTSL/UnifierTSL.csproj -- \
      -port 7777 -password changeme -server "name:Dev worldname:Dev"
    Useful for quick validation before publishing.
  4. Observe console isolation
    • Start the launcher (step 3) and note that it spawns one console client process per hosted world.
    • Manual execution of UnifierTSL.ConsoleClient is not supported; it expects the pipe identifiers supplied by the launcher during process creation.

For more information on the output behaviors that Publisher can control? Refer to the Publisher Output Behavior section in the plugin-dev-doc

Launcher Cheatsheet

These flags feed the launcher everything it needs to start, secure, and route worlds.

Flag(s) Description Values Default or Notes
-listen, -port Set the TCP port used by UnifiedServerCoordinator Integer between 1 and 65535 Prompts on STDIN until a valid value is supplied if omitted
-password Set the connection password shared with clients Any string, quotes allowed Prompts on STDIN if omitted
-autostart, -addserver, -server Queue one or more server definitions for launch Repeatable; each value uses key:value pairs described below Invalid definitions are rejected with a console warning
-joinserver Register a low priority handler that selects the server a player joins by default first, f, random, rnd, or r Only the first valid value is applied
-culture, -lang, -language Override the Terraria localization used for server text Integer IDs accepted by GameCulture._legacyCultures Defaults to the game culture configured on the host

Important: Unless a plugin registers a preferred join server through EventHub.Coordinator.SwitchJoinServer, pass -joinserver first (or random) so connecting players are routed into a valid server instance. Without it, players will be kicked with the message "Unable to locate an available server to join."

Server Definition Keys

Each -server (or -autostart/-addserver) entry is a space separated list of key:value pairs. Supported keys are parsed in UnifierApi.AutoStartServer.

Quick shorthand: wrap values containing spaces (for example world names or seeds) in quotes so the shell keeps each key:value pair together.

Key Purpose Accepted Values Notes
name Friendly server identifier Unique string Required; conflicts cause the entry to be skipped
worldname World name to create or load Unique string Required; conflicts with existing worlds abort the entry
seed Generation seed Any string Optional; defaults to empty
gamemode or difficulty World difficulty 0-3, normal, expert, master, creative, or shorthand n, e, m, c Defaults to 2 (Master)
size World size 1-3, small, medium, large, or shorthand s, m, l Defaults to 3 (Large)
evil World evil setting 0-2, random, corruption, crimson Defaults to 0 (Random)

What's in the Bundle

The publisher will build the deployment package in the bin/<Config>/net9.0/utsl-<rid> directory. Its general structure is as follows, which can be used for a quick sanity check:

UnifierTSL.exe / UnifierTSL   # Platform specific launcher entry point
app/
  UnifierTSL.ConsoleClient.*  # Console client binaries spawned by the launcher per server context
config/
  TShockAPI/                  # TShock configs, database, SSC, MOTD, rules
  ExamplePlugin/              # Sample plugin configuration (remove if unused)
  CommandTeleport/            # Additional plugin state and settings
lib/                          # Shared managed libraries
plugins/
  ExamplePlugin.dll
  CommandTeleport.dll
  TShockAPI/
    TShockAPI.dll
    dependencies.json         # Generated at runtime after ModuleLoadContext resolves and stages dependencies
runtimes/                     # Platform specific native assets (for example win-x64/)
start.bat / launch.sh         # Helper scripts illustrating CLI usage (create locally as needed)

Note: Keep plugins/ and config/ aligned so each plugin can locate configuration and dependency files after deployment.

Project Layout

Work from these folders when navigating the repository:

  • src/UnifierTSL.sln brings together the launcher, console client, publisher, and sample plugins.
  • src/UnifierTSL/ hosts runtime entry points plus subsystems such as Module/, PluginHost/, Servers/, and Network/.
  • src/UnifierTSL.ConsoleClient/ contains the per-instance console isolation client and its named pipe protocol; the launcher is responsible for invoking it.
  • src/UnifierTSL.Publisher/ packages bundles to bin/<Config>/net9.0/utsl-<rid>.zip with RID targeted assets.
  • src/Plugins/ provides maintained examples (ExamplePlugin, CommandTeleport, TShockAPI) that serve as scaffolds for new integrations.
  • doc/ stores project documentation, including this overview and design notes.

Batteries Included

UnifierTSL ships with these companion projects so you do not have to stitch tooling together yourself:

  • Launcher (src/UnifierTSL/) handles USP hosting, world lifecycle management, and plugin bootstrap through UnifiedServerCoordinator.
  • Console client (src/UnifierTSL.ConsoleClient/) isolates each server context's console I/O through named pipes, keeping simultaneous sessions readable.
  • Logging core (src/UnifierTSL/Logging/) centralizes the shared Logger so filters, writers, and metadata injectors can be swapped or extended by plugins via UnifierApi.CreateLogger.
  • Publisher (src/UnifierTSL.Publisher/) builds self-contained distributables and can exclude plugins per run.

Plugin Feature Overview

Dropping a plugin DLL into plugins/ is enough for UnifierTSL to discover it, but the loader immediately tidies files so the runtime can manage hot reloads and dependencies. If a file vanishes from the location you copied it to, check the subfolders below—UnifierTSL has simply moved it into the layout it expects.

Runtime Module Types

  • Core modules ([CoreModule]): Act as the anchor for related code. The loader creates plugins/<ModuleName>/, keeps the DLL there, and gives it a dedicated collectible AssemblyLoadContext. Core modules can declare NuGet or embedded dependencies with [ModuleDependencies]; the loader extracts them into lib/ and tracks versions in dependencies.json.
  • Dependent modules ([RequiresCoreModule("MainName")]): Must point at an existing core module. They are moved into the core module’s folder and loaded through the same AssemblyLoadContext, automatically reusing the dependencies the core module declared. They cannot add new dependency declarations of their own.
  • Independent modules (no [CoreModule] or [RequiresCoreModule]): Load in their own context and cannot be targeted by dependent modules. Otherwise they behave like a self-contained core module—if they declare [ModuleDependencies], they get a private folder and dependency staging; if not, they stay wherever you copied them.

What Happens in plugins/

  • Initial scans cover both the root and existing subdirectories. If the loader spots a core attribute, dependency attribute, or RequiresCoreModule, it moves the DLL (and its PDB) into the appropriate folder before loading it.
  • Core modules and independent modules with dependency declarations end up as plugins/<ModuleName>/<ModuleName>.dll, accompanied by lib/ folders when dependencies are present.
  • Dependent modules sit alongside their core module but keep their own file name, so features such as ExamplePlugin.Features.dll travel with the ExamplePlugin directory while remaining easy to identify.
  • Independent modules without dependency declarations remain exactly where you placed them until you add metadata that changes how they should load.

Configuration Files

  • Every plugin gets a private configuration directory under config/, named after the DLL without its extension. That is why ExamplePlugin.Features.dll stores settings in config/ExamplePlugin.Features/, separate from config/ExamplePlugin/.
  • Plugins decide whether a manual edit takes effect immediately. The configuration system raises file-change notifications, but auto-reloading is opt-in; many plugins persist the new values only after they call TriggerReloadOnExternalChange(true) or offer an in-game reload command. The bundled ExamplePlugin enables instant reload and logs changes, while the integrated TShock build also taps into UnifierTSL's auto reload (upstream TShock still follows its own flow).
  • If a plugin does not automatically reload, restart the plugin or the launcher to apply edits safely.

Bundled Examples

  • ExamplePlugin.dll: A core module that bootstraps configuration helpers and exposes shared tools for satellite plugins.
  • ExamplePlugin.Features.dll: A dependent module that extends the main plugin and loads only after ExamplePlugin finishes initializing.
  • CommandTeleport.dll: An independent plugin that hooks into the multi-world coordinator; it can stand alone or declare its own dependencies.
  • TShockAPI.dll: A core module that stages database drivers and HTTP components inside its lib/ directory, making them available to its dependents and any runtime assembly resolution.

Using Assemblies as Shared Helpers

  • You can drop managed assemblies—such as Newtonsoft.Json.dll—directly into plugins/. They are discovered like any other module even if they are not a plugin entry point.
  • When another module requests an assembly, UnifierTSL first prefers an exact version match from already loaded modules, then looks at the dependencies declared by the requesting module, and finally falls back to name-only matches across the staged assemblies. This strategy avoids loading the same DLL into multiple AssemblyLoadContext instances and helps reduce memory usage for shared libraries.

Developer Reference

Keep these commands in your terminal history while working on the project:

  • dotnet restore src/UnifierTSL.sln restores all solution dependencies.
  • dotnet build src/UnifierTSL.sln -c Debug [-warnaserror] builds the workspace with optional analyzer enforcement.
  • dotnet run --project src/UnifierTSL/UnifierTSL.csproj launches the server; omit arguments to accept the interactive prompts, or append -- followed by CLI flags such as -port 7777.
  • dotnet run --project src/UnifierTSL.Publisher/UnifierTSL.Publisher.csproj -- --rid win-x64 produces a distributable bundle under src/UnifierTSL.Publisher/bin/<Config>/net9.0/utsl-win-x64.zip; combine with --excluded-plugins ExamplePlugin as needed.
  • dotnet run --project src/UnifierTSL.ConsoleClient/UnifierTSL.ConsoleClient.csproj requires the pipe arguments injected by the launcher; run the launcher instead to exercise console isolation.

Testing roadmap: No automated tests exist yet. Start with dotnet new xunit -n UnifierTSL.Tests -o tests/UnifierTSL.Tests, mirror runtime namespaces (for example Module/ to ModuleTests/), and run dotnet test src/UnifierTSL.sln after new work.

Publisher CLI Reference

Use these switches to tailor bundles without editing code:

Flag Description Values Notes
--rid Target runtime identifier passed to CoreAppBuilder, AppToolsPublisher, and plugin bundlers Required single value such as win-x64, linux-x64, osx-x64 Throws if omitted or provided more than once (Program.cs)
--excluded-plugins Comma separated list of plugin names to skip Optional; accepts multiple occurrences or comma lists Parsed into trimmed entries before invoking PluginsBuilder

Tip: Combine --rid with the DOTNET_CLI_TELEMETRY_OPTOUT environment variable in CI so your build logs stay quiet and reproducible.

Keep Exploring

Round out your knowledge with these guides:

About

A cross-platform Terraria server launcher built on UnifiedServerProcess, with multi-world support, per-instance consoles, and hot-reload plugin architecture.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages