Skip to content

Stationeers game logging and diagnostic output sinks

The Stationeers game writes diagnostic and telemetry data to multiple log sinks. This page catalogs every known log file and output mechanism the game itself produces.

For which sink a MOD should write to, and the traps in the in-game console API, see InGameConsoleOutput. For the ConsoleWindow class reference, see ConsoleWindow.

Summary table

Log sink File path Condition Content Writer
Player.log %USERPROFILE%\AppData\LocalLow\Rocketwerkz\rocketstation\Player.log Always active UnityEngine.Debug.Log* and BepInEx plugin logs (via BepInEx UnityLogListener) Unity player runtime (built-in)
Player-prev.log Same folder Auto-rotated Previous session Player.log Unity built-in rotation
Console export <USER_DOCUMENTS>\My Games\Stationeers\PlayerLog.txt_*.log (the Documents folder; %USERPROFILE%\Documents by default) Player-initiated via log command In-game console buffer dump LogCommand.LogToFile
Harmony FileLog Desktop\harmony.log.txt OR HARMONY_LOG_FILE env var No switch: writes whenever something calls it (a mod directly, or a method a mod patched with the DebugLogger transpiler); only FileLog.Debug waits for Harmony.DEBUG Whatever the caller logs HarmonyLib.FileLog
HarmonyFileLog CWD\HarmonyLog.txt OR HarmonyFileLog.FileWriterPath Opt-in (HarmonyFileLog.Enabled=true, which Harmony.DEBUG or HARMONY_DEBUG also sets when a Harmony instance is created) HarmonyX Logger messages that pass Logger.ChannelFilter, or that a patch's debug flag forces through HarmonyLib.Tools.HarmonyFileLog
Dedicated server console Console window (live, not persisted) Batch mode without -logFile ConsoleWindow output to system console RocketSystemConsole
Custom log file Specified via -logFile flag Batch mode with -logFile UnityEngine.Debug.Log* calls UnityEngine.Debug
In-game console buffer In memory (1024 lines) Always active, non-batch ConsoleWindow.Print* calls, command output, AND Debug.LogError / LogException re-printed by the log bridge ConsoleWindow

The last row is the one that most often surprises a mod author: the in-game console is a consumer of Unity's log stream, not only of explicit Print calls. See "The log bridge" below.

Detailed findings

1. Player.log (primary)

  • Path: %USERPROFILE%\AppData\LocalLow\Rocketwerkz\rocketstation\Player.log
  • Rotation: Auto-rotated to Player-prev.log on each launch
  • Content: UnityEngine.Debug.Log/LogWarning/LogError output
  • Additional: BepInEx plugin logs, written by BepInEx's own UnityLogListener (an ILogListener in BepInEx.dll, alongside ConsoleLogListener and DiskLogListener). It formats each event with [{0,-7}:{1,10}] {2} (level left-padded to 7, source right-padded to 10) and passes the result to Unity's internal UnityEngine.UnityLogWriter.WriteStringToUnityLog, a native binding exported by UnityEngine.CoreModule.dll (Runtime/Export/Logging/UnityLogWriter.bindings.h). That call writes the string straight into the player log; it does not route through Debug.unityLogger, so it raises no logMessageReceivedThreaded event, captures no stack trace, and is never re-printed into the in-game console at any severity. StationeersLaunchPad plays no part: BepInEx-format lines appear in Player.log before StationeersLaunchPad's assembly is loaded, and StationeersLaunchPad.decompiled.cs contains no reference to BepInEx.Logging at all.
  • Writer: the Unity player runtime itself, internally. No game code writes this file.

2. The log bridge: ConsoleWindow consumes Unity's log stream

ConsoleWindow._Init subscribes to Application.logMessageReceivedThreaded (Assembly-CSharp:221927) and its LogMessage handler (Assembly-CSharp:222266-222282) re-prints LogType.Error and LogType.Exception into the in-game console buffer, lowercased, prefixed [ERROR] / [EXCEPTION], red, followed by the stack trace in the default colour.

This subscription is a consumer of Unity's log notification event. It does not write Player.log; Unity does that internally regardless of who subscribes. Getting this backwards is easy because both facts involve the same event name.

  • Reaches the in-game console: LogType.Error, LogType.Exception.
  • Does not: LogType.Log, LogType.Warning, LogType.Assert.
  • Suppressed entirely when CustomLogFile is true (Assembly-CSharp:221915, CommandLineArgs?.Contains("-logFile") ?? false), which breaks a recursion loop: under batch + CustomLogFile, ConsoleWindow.Print itself calls UnityEngine.Debug.LogError/LogWarning/Log.
  • The non-threaded Application.logMessageReceived is never subscribed anywhere in the assembly.

Consequence for mods: a Debug.LogError is player-visible, and pairing it with a ConsoleWindow call for the same text double-prints.

BepInEx Log.LogError does not travel this route, so pairing it with a ConsoleWindow call is safe. This is the fact the whole "log to the file, print to the console" pattern rests on, and it is worth stating as a verified negative rather than an assumption. StationeersLaunchPad.decompiled.cs contains no subscription to BepInEx ManualLogSource.LogEvent, no ILogListener implementation, and no reference to BepInEx.Logging at all. Its only re-emission into Unity's log stream is Logger.LogUnityInternal (:2905, a Debug.LogFormat call), reachable only from Logger.Log(..., unity: true), which requires a mod to call StationeersLaunchPad's own Logger directly rather than its BepInEx ManualLogSource. So a plugin's Log.LogError(...) never becomes a Debug.LogError, never raises logMessageReceivedThreaded, and never reaches the in-game console. Log.LogError(msg) alongside ConsoleWindow.PrintError(msg, suppressStacktrace: true) yields exactly one console line and one log line, which is the intended shape.

The one in-repo counter-example is a mod that deliberately bridges the two itself: Plans/MaintenanceBureauPlus/MaintenanceBureauPlus/LaunchPadLog.cs hooks its own BepInEx ManualLogSource and forwards every entry into ConsoleWindow.Print by reflection. That is the mod's own wiring, not a StationeersLaunchPad behaviour.

3. The reverse bridge: Unity to BepInEx, and why it does not reach LogOutput.log

Unity's log stream is also fed INTO the BepInEx logging system, but at default settings it does not reach LogOutput.log. [Logging] UnityLogListening (default true) installs UnityLogSource, which captures Debug.Log* output as BepInEx log events. [Logging.Disk] WriteUnityLog (default false, described in BepInEx.cfg as "Include unity log messages in log file output.") then makes DiskLogListener discard them. Captured, not written.

Verified against a live session by counting the same markers in both files:

Marker Player.log LogOutput.log
^\[Global\]: (StationeersLaunchPad via Debug.LogFormat) 22 0
Begin MonoManager (Unity engine) 1 0
UnloadTime (Unity engine) 2 0

The practical consequence, and the reason this matters for mod code: the useful direction is BepInEx to Unity, not Unity to BepInEx. One Log.LogError(msg) on a plugin's ManualLogSource lands in BOTH LogOutput.log (via DiskLogListener) and Player.log (via UnityLogListener), from a single call, at every severity, with no console side effect. One Debug.LogError(msg) lands only in Player.log and additionally double-prints into the in-game console. So a mod that wants its output readable afterwards should prefer its BepInEx ManualLogSource over UnityEngine.Debug.* for everything.

Two traps in the same area:

  • LogMessage lowercases the message before printing it (logStr.ToLower(), Assembly-CSharp:222273), so a Debug.LogError shows the player mangled casing for mod names, device names, and IC10 tokens.
  • Debug.LogAssertion and Debug.Assert are [Conditional("UNITY_ASSERTIONS")], evaluated at the caller's compile time. A mod whose .csproj does not define UNITY_ASSERTIONS has the call removed by its own compiler and the message reaches nothing at all. Across the whole game assembly there are 49 Debug.Log(, 36 Debug.LogWarning(, 97 Debug.LogError( and 35 Debug.LogException( call sites and zero of either assertion form, which is the signature of that stripping.

4. Player-prev.log

  • Path: Same directory as Player.log
  • Content: Previous session's Player.log (Unity automatic rotation)

5. Console buffer export (PlayerLog.txt_<timestamp>.log)

  • Path: <USER_DOCUMENTS>\My Games\Stationeers\, where <USER_DOCUMENTS> is the user's Documents folder: %USERPROFILE%\Documents on a default Windows install, elsewhere if Documents has been moved or redirected (see Defines.Paths.LocalData under "Key path constant")
  • Invocation: In-game console command: log, or log followed by name arguments (a lone name argument is ignored; see Custom naming)
  • Content: Entire 1024-line in-game console buffer with timestamps. Each non-empty line is written as ConsoleLine.ToString(), which is Time + ": " + Text (Time is stamped HH:mm:ss when the line is set), followed by any continuation lines. An empty buffer prints No console text to export. and writes nothing.
  • Implementation: LogCommand class (Assembly-CSharp:99884), dispatching LogToFile(args).Forget() at :99900 into private static async UniTaskVoid LogToFile(string[] lineSplit) at :99915. Line numbers in this item are from the 0.2.6428.27798 client decompile.
  • Default naming: PlayerLog.txt_yyyy-MM-dd_HH-mm-ss.log, for example PlayerLog.txt_2026-09-21_14-05-33.log. The default is string.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}", "PlayerLog.txt", DateTime.Now), from DEFAULT_LOG_NAME = "PlayerLog.txt", and the later text.Replace(".log", "") strips only .log, so the .txt stays inside the name.
  • Custom naming: Spaces replaced with underscores, .log suffix added; single and double quotes are stripped. The argument that supplies the name is chosen wrongly, though. Execute receives the arguments without the command word, and the naming loop is for (int i = 1; i < lineSplit.Length; i++) calling CommandBase.Get(lineSplit, 1, "logname", out string result) with the fixed index 1 on every pass. So log mylog ignores mylog and writes the default name, log a b writes b.log, and log a b c writes b_b.log. Quoting does not help: CmdLineParser.SplitCommandLine keeps "my log" together as one argument, which is ignored when it is the only one; log x "my log" writes my_log.log.
  • Existing file: deleted first, then written afresh.
  • Confirmation: the console prints Copied '<count>' lines from console to '<path>'. with the full path, so it shows where the file landed.
  • Related: log clear command deletes all *.log files in LocalData folder

6. Harmony FileLog

  • Path: %USERPROFILE%\Desktop\harmony.log.txt (the folder from Environment.SpecialFolder.Desktop, created first if missing)
  • Override: Environment variable HARMONY_LOG_FILE, read once in the static constructor (0Harmony line 8550)
  • Activation: none. Log(string) and FlushBuffer() append to the file every time they are called. Only Debug(string) checks Harmony.DEBUG (line 8659), and nothing in 0Harmony calls Debug. Harmony.DEBUG does not switch this file on; it switches on HarmonyFileLog (item 7).
  • Who calls it: inside 0Harmony, only the DebugLogger transpiler helper (lines 4843-4851). It inserts a FileLog.Log call at the start of the patched method, so the line is written each time that method runs, not when the patch is applied. Anything else comes from a mod calling FileLog directly. The doc comments on [HarmonyDebug] and on Patch.debug and its constructor parameters still say that output goes to FileLog on the Desktop (lines 2113, 3675, 3720, 3743, 3768); no code in this build does that. The debug flag is handed to HarmonyLib.Tools.Logger as forcePropagation instead (item 7).
  • Content: whatever the caller passes. HarmonyX writes no patch traces here on its own.
  • Implementation: HarmonyLib.FileLog class (0Harmony:8529-8715), in $(StationeersPath)\BepInEx\core\0Harmony.dll (HarmonyX 2.9.0)
  • Methods:
  • Log(string) - immediate disk write via File.AppendText (line 8650)
  • LogBuffered(string) - queued; FlushBuffer() writes the queue with File.AppendText (line 8633)
  • Debug(string) - calls Log(string) only if Harmony.DEBUG is true
  • LogBytes(long, int) - hex dump written through Log(string)
  • Reset() - deletes harmony.log.txt from the Desktop (line 8670), even when HARMONY_LOG_FILE points elsewhere
  • Thread safety: Guarded by lock object fileLock

7. HarmonyFileLog (HarmonyLog.txt)

  • Path: CWD\HarmonyLog.txt. The file is Path.GetFullPath(FileWriterPath), and FileWriterPath defaults to "HarmonyLog.txt" (0Harmony:9332), so it lands in the process working directory unless the property is changed before the log is enabled. A caller can instead assign any TextWriter to HarmonyFileLog.Writer, and then no file is created.
  • Activation: HarmonyFileLog.Enabled = true (not default; BepInEx.dll never sets it). The Harmony(string id) constructor also sets it when Harmony.DEBUG is true (lines 2577-2580), and first sets Harmony.DEBUG from a HARMONY_DEBUG environment variable when one is present (1 or true turn it on, false turns it off; lines 2567-2572). Harmony.DEBUG is [Obsolete("Use HarmonyFileLog.Enabled instead")] (line 2545) and is turned into Enabled only inside that constructor, so setting it takes effect at the next new Harmony(...).
  • Content: messages raised through HarmonyLib.Tools.Logger: only those whose channel passes Logger.ChannelFilter, plus those a patch's debug flag ([HarmonyDebug]) force-propagates. 0Harmony never assigns ChannelFilter. Under BepInEx it is set from [Harmony.Logger] LogChannels in BepInEx.cfg (default Warn, Error) by BepInEx's HarmonyLogSource, which also forwards those channels into the BepInEx log as source HarmonyX, so HarmonyX warnings and errors already reach LogOutput.log and Player.log ([Warning: HarmonyX] ...) with this file off. How the channel filter and that bridge interact: HarmonyPatchAllIsolation, "Why the log looks empty".
  • Implementation: HarmonyLib.Tools.HarmonyFileLog class (0Harmony:9291-9355; namespace HarmonyLib.Tools opens at line 9286). No class named FileWriter exists in the assembly; this page used that name until 2026-09-21, taken from the FileWriterPath property.
  • Operation: on enable, if no writer is set, creates one with new StreamWriter(File.Create(Path.GetFullPath(FileWriterPath))) (line 9340), which truncates an existing file, then subscribes to Logger.MessageReceived (line 9342)
  • Format: [{e.LogChannel}] {e.Message}, flushed after every line (line 9352)
  • Duplicate lines: the Enabled setter subscribes with += every time it is set to true and never checks for an existing subscription. With Harmony.DEBUG on, every Harmony instance created afterwards sets it again, so each message is written once per handler. Read from the code, not observed.

8. Dedicated server console (RocketSystemConsole)

  • Activation: Batch mode (GameManager.IsBatchMode) without -logFile flag
  • Path: System console window (live output, not persisted by default)
  • Content: All ConsoleWindow.Print* calls + console command input/output
  • Implementation: RocketSystemConsole class (Assembly-CSharp:109870); PrintToConsole at :110054, which is a plain System.Console.WriteLine at :110066, with its own pre-ready queue at :110056-110060
  • Platform support: Windows and Linux server builds. The constructor throws "Don't use this outside of dedicated server builds" unless Application.platform is LinuxServer or WindowsServer.
  • Features:
  • UTF-8 encoding
  • Input thread for console commands
  • Title bar with game version and command-line args
  • Input forwarded directly to CommandLine.Process, by _systemConsoleInput.OnInputReceived += CommandLine.Process;. It does NOT go through ConsoleWindow.Submit, which this page claimed until 2026-08-15; Submit is the in-game console's own path and prints an echo the console channel does not.
  • Initialization: ConsoleWindow._Init, inside the IsBatchMode branch and conditional on !CustomLogFile (Assembly-CSharp:221929-221937; the new RocketSystemConsole(...) is at :221936)
  • Important: This is live console, not persisted to disk unless stdout redirected
  • Caveat: PrintBlock and PrintSegmentedBlock skip their entire body when IsBatchMode (Assembly-CSharp:223036), so they produce NO dedicated-server output. Only the plain Print has a batch-mode sink.

The input side is keystrokes, not a stream, and it cannot be driven by writing to the process's stdin. Verified at 0.2.6428.27798 against the DEDICATED SERVER build (.work/decomp/0.2.6428.27798/Assembly-CSharp.DedicatedServer.decompiled.cs; line numbers in this paragraph are from that file, not from the client decompile the rest of this section cites):

  • RocketSystemConsole.ConsoleInputThread (:107330) loops on System.Console.CursorVisible and System.Console.KeyAvailable and calls System.Console.ReadKey() (:107335, :107339). Those are the Win32 console input buffer, reached through PeekConsoleInput / ReadConsoleInput. An anonymous pipe on the child's stdin is a different object and is never read. ReadKey and KeyAvailable each occur exactly once in the whole assembly, both here.
  • Enter triggers OnEnter(), which strips the "> " prefix, trims, and enqueues under lock (_inputQueue). A separate Tick() loop drains that queue only when running on the Unity thread, then raises OnInputReceived. That marshalling is why console commands are safe against game state.
  • _inputThread.Start() is called from exactly one place, Ready() (:107307), which is itself called only from ConsoleWindow.WaitForGameToBeReadyThenOverrideConsoleInput() (:215503) after GameManager.IsInitialized plus a 1 s delay, and under the same !CustomLogFile guard. With -logFile on the launch line there is no console object, no input thread and no handler.
  • Measured 2026-08-15 on 0.2.6428.27798, -logFile REMOVED so the console was fully up (stdout carried ***Stationeers - 0.2.6428.27798*** then Ready): settings ServerMaxPlayers 9 written to the process's redirected stdin and flushed changed nothing, and printed nothing. Full write-up and the paired control arms: DedicatedServerSettings, "The console channel".

9. Custom log file (-logFile flag)

  • Activation: Batch mode with -logFile command-line flag
  • Detection: ConsoleWindow.CustomLogFile property (Assembly-CSharp:221915): CommandLineArgs?.Contains("-logFile")
  • Path: Specified by flag or Unity default output_log.txt
  • Content: UnityEngine.Debug.Log* calls (routed from ConsoleWindow.Print)
  • Behavior: Skips RocketSystemConsole creation (:221934-221936); also disables the log bridge in section 2
  • Routing: ConsoleWindow.Print routes to Debug.LogError / LogWarning / Log by colour band (Assembly-CSharp:222968-222986). Without -logFile, the batch path instead writes to the system console and silently DROPS any line containing <color= (:222987).

Additional command-line flags

Flag Scope Effect
-logFile Batch mode Routes output to log file instead of system console; also disables the ConsoleWindow log bridge
-noclear Batch mode Prevents console window clearing on startup
HARMONY_LOG_FILE (env var) Global Overrides default harmony.log.txt path

Key path constant

Defines.Paths.LocalData (Assembly-CSharp:234204 in the 0.2.6428.27798 client decompile; Paths is nested in Assets.Scripts.Util.Defines), verbatim, with the field it appends (:234202):

            private static string _baseFolder = "\\My Games\\Stationeers\\";

            public static string LocalData = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + _baseFolder;

Resolves to <USER_DOCUMENTS>\My Games\Stationeers\, where <USER_DOCUMENTS> is the user's Documents folder: %USERPROFILE%\Documents\My Games\Stationeers\ on a default Windows install.

Documents is looked up at runtime, not assumed. In the game's mscorlib.dll (Unity's Mono class library), System.Environment.GetFolderPath on Windows returns GetWindowsFolderPath((int)folder), an internal call into the Mono runtime, and the runtime (MonoBleedingEdge\EmbedRuntime\mono-2.0-bdwgc.dll in the install) imports SHGetFolderPathW and SHGetKnownFolderPath from SHELL32.dll. Those calls report where Documents currently is, so a Documents folder that has been moved or redirected (OneDrive folder backup, a folder-redirection policy, a manual move to another drive) takes this folder with it. That consequence rests on the documented behaviour of the two shell calls; it has not been observed, because the machine this was checked on keeps Documents at its default location. Write the location as the Documents folder, not as a fixed %USERPROFILE%\Documents path.

Not covered

  • Crash dumps (.dmp files) - OS-level, not game-code initiated
  • Steam telemetry - third-party, not visible in game code
  • Mod logging - documented in InGameConsoleOutput.md
  • Network/RakNet logs - no explicit log file exposed in decompiled code

Verification history

  • 2026-09-21 (later): conflict on "what the two Harmony log rows describe, and where 0Harmony.dll lives". Three conflicts, each put to its own fresh validator using the Research/WORKFLOW.md template with the two source labels removed, so no validator could tell which claim was on the page. (1) FileLog and Harmony.DEBUG. Previous claim (summary table row "Harmony debug log", detail item 6): HarmonyLib.FileLog writes Desktop\harmony.log.txt "Only if Harmony.DEBUG = true", holding "Low-level transpiler and patch-application traces". New finding: FileLog has no switch, Log and FlushBuffer write whenever they are called, only FileLog.Debug(string) checks Harmony.DEBUG, and the only caller inside 0Harmony is the DebugLogger transpiler helper; Harmony.DEBUG is [Obsolete("Use HarmonyFileLog.Enabled instead")], and the Harmony(string id) constructor turns it, or HARMONY_DEBUG, into HarmonyFileLog.Enabled = true, which writes HarmonyLog.txt. Fresh validator verdict: B is correct, quoting FileLog.Log and FileLog.Debug (if (Harmony.DEBUG) { Log(str); }, :8646-8663) and the constructor's if (DEBUG) { HarmonyFileLog.Enabled = true; } (:2577-2580). It added that nothing in the assembly calls FileLog.Debug, that the DebugLogger call is written into the patched method and so logs each time that method runs, that Reset() deletes the Desktop file even when HARMONY_LOG_FILE points elsewhere, and that the doc comments on [HarmonyDebug] and Patch.debug promising Desktop FileLog output (:2113, :3675, :3720, :3743, :3768) describe no code in this build, which is the likely origin of the old claim. (2) The HarmonyLog.txt writer. Previous claim (row "Harmony FileWriter", detail item 7): class HarmonyLib.FileWriter, "Only if FileWriter.Enabled = true", path overridable through FileWriter.FileWriterPath. New finding: the class is HarmonyLib.Tools.HarmonyFileLog (:9291), no FileWriter class exists, FileWriterPath defaults to "HarmonyLog.txt" (:9332), and Harmony.DEBUG and HARMONY_DEBUG also switch it on. Fresh validator verdict: B is correct, quoting namespace HarmonyLib.Tools through public static class HarmonyFileLog (:9286-9291), public static string FileWriterPath { get; set; } = "HarmonyLog.txt"; (:9332) and Writer = new StreamWriter(File.Create(Path.GetFullPath(FileWriterPath))); (:9340); a whole-word search of the decompile finds no FileWriter. It added that no file is created when a caller has already set Writer, that File.Create truncates, that nothing in 0Harmony assigns Logger.ChannelFilter, and, from the code rather than a run, that each Enabled = true adds another MessageReceived handler. (3) Where the DLL is. Previous claim (frontmatter sources): $(StationeersPath)\rocketstation_Data\Managed\0Harmony.dll. New finding: the only 0Harmony.dll in the install is BepInEx\core\0Harmony.dll. Fresh validator verdict: B is correct, quoting a recursive search of the install for 0harmony* that returns BepInEx/core/0Harmony.dll, BepInEx/core/0Harmony.xml and BepInEx/core/0Harmony20.dll and nothing else; rocketstation_Data\Managed\ holds 146 files, none with "harmony" in the name. BepInEx\core\0Harmony.dll is assembly 0Harmony 2.9.0.0, product HarmonyX, with an attribute block matching the decompile's; 0Harmony20.dll is a different assembly (0Harmony20 2.0.0.0, product HarmonyX2Interop). The dedicated server install and the rig's client installs also keep it only under BepInEx\core\, compared by file size only. Wrong when written, not changed by an update: the page's first version (2026-06-24, against 0.2.6228.27061) already named FileWriter and cited :9332-9355, the same range it later cited against 0.2.6403.27689 and the range that today holds FileWriterPath through the end of HarmonyFileLog; the DLL is BepInEx's HarmonyX 2.9.0, which does not change with the game version. Result: summary table rows 4-5 and detail items 6-7 rewritten from the verdicts, with item 7 pointing at HarmonyPatchAllIsolation for how BepInEx's HarmonyLogSource sets the channel filter; the frontmatter sources corrected (DLL path, class names, current line ranges) and the BepInEx decompile added. Items 6 and 7 restamped to 0.2.6428.27798 @ 2026-09-21. The summary table is restamped too: rows 4-5 on this pass, the "Player.log" row against the UnityLogListener body below, and the other rows on the strength of the re-read recorded in the entry below this one. Detail item 1 keeps its 0.2.6403.27689 stamp, because its path and rotation claims were not re-checked. The Open question on the Harmony rows is removed. Also resolved: the Open question "UnityLogListener.LogEvent's method body has not been read". .work/decomp/0.2.6428.27798/BepInEx.decompiled.cs (BepInEx 5.4.23.5) now exists, and the body (:3239-3245) is if (!(eventArgs.Source is UnityLogSource) && (LogConsoleToUnity.Value || eventArgs.Source.SourceName != "Console")) { WriteStringToUnityLog?.Invoke(eventArgs.ToStringLine()); }, where ToStringLine() is ToString() plus a newline and ToString() is $"[{Level,-7}:{Source.SourceName,10}] {Data}" (:2690-2702). That confirms detail item 1 from the method itself: every BepInEx log event goes to that writer except events from UnityLogSource, which would loop, and, unless [Logging] LogConsoleToUnityLog is on, events from the Console source. The current .work/decomp/0.2.6428.27798/StationeersLaunchPad.decompiled.cs (0.5.1.0) still has no reference to BepInEx.Logging, ILogListener, LogEvent or ManualLogSource. The "which overload binds" question stays open, rewritten with the probe order the static constructor uses.
  • 2026-09-21: conflict on "where the log console command writes its export, and what it names the file". Two separate conflicts, each put to its own fresh validator using the Research/WORKFLOW.md template with the two source labels removed, so the validator could not tell which claim was already on the page. (1) The folder. Previous claim (summary table row "Console export", detail item 5 and "Key path constant", all stamped 0.2.6403.27689): %USERPROFILE%\My Games\Stationeers\. New finding: Defines.Paths.LocalData is Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + _baseFolder with _baseFolder = "\\My Games\\Stationeers\\" (:234202-234204 in the 0.2.6428.27798 client decompile), so the folder sits inside the user's Documents folder, %USERPROFILE%\Documents\My Games\Stationeers\ by default. Fresh validator verdict: B is correct, quoting those two lines. It also read System.Environment.GetFolderPath in the game's mscorlib.dll through an in-memory decompile (no file written): on Windows it returns GetWindowsFolderPath((int)folder), an internal call, and only the non-Windows branch maps MyDocuments to the home directory, the one case where the old path shape would hold. It parsed the import table of mono-2.0-bdwgc.dll (SHGetFolderPathW and SHGetKnownFolderPath from SHELL32.dll), and on the development machine found Documents\My Games\Stationeers\ present with a setting.xml written that day, while %USERPROFILE%\My Games\Stationeers\ does not exist. It did not establish which of the two shell calls runs, and Documents is not redirected on that machine, so the statement that the folder follows a redirected Documents folder rests on the documented behaviour of those calls rather than on an observation. (2) The file name. Previous claim (detail item 5 and its heading, and PlayerLog_*.log in the summary table): default name PlayerLog_YYYY-MM-DD_HH-mm-ss.log, and log <customname> names the file. New finding: the default is PlayerLog.txt_yyyy-MM-dd_HH-mm-ss.log, and a single argument is ignored because the naming loop starts at index 1 and always reads lineSplit[1]. Fresh validator verdict: B is correct, quoting the LogToFile naming block (:99917-99933) and tracing the console line through ConsoleWindow.Submit, CommandLine.Process(string), CommandLine.Process(string[], bool) and ExecuteCommand to Execute(args) with the command word removed; an exact-match search of the installed Assembly-CSharp.dll strings found no PlayerLog_ at all, only PlayerLog.txt. Whether the old name was ever right is not established: the LogCommand lines this page cited from the 0.2.6403.27689 decompile (class :99289, dispatch :99305, LogToFile :99320) sit at the same distances apart as in 0.2.6428.27798 (:99884, :99900, :99915), which fits an unchanged class, but that decompile no longer exists. Result: the summary table's "Console export" row, detail item 5 (heading included) and "Key path constant" corrected. Detail item 5 also gained the argument-selection bug, the quote stripping, the delete-then-write, the empty-buffer message and the confirmation line that prints the full path; "Key path constant" gained the _baseFolder line and the resolution mechanism. Detail item 5 and "Key path constant" were re-read in full against 0.2.6428.27798 and restamped, with their line citations moved to that decompile. The summary table's stamp was deliberately left at 0.2.6403.27689 @ 2026-07-29 even though one row changed: re-reading the other rows against 0.2.6428.27798 found no discrepancy except in the two Harmony rows, which do not match the 0.2.6428.27798 0Harmony decompile (see Open questions), and restamping the table would claim those rows as verified. No other section was re-read.
  • 2026-08-15: conflict on "how does the dedicated server's console input reach the command dispatcher". Previous claim (detail item 8, stamped 0.2.6403.27689): "Input forwarded to CommandLine.Process via ConsoleWindow.Submit (Assembly-CSharp:222487)". New finding, from a fresh validator's own decompile of the DEDICATED SERVER Assembly-CSharp.dll at 0.2.6428.27798: the console subscribes directly, _systemConsoleInput.OnInputReceived += CommandLine.Process; (:215509), and never touches Submit. Verdict: the new finding is correct; the token CommandLine.Process occurs exactly four times in the assembly and :215509 is one of them, while ConsoleWindow.Submit's own call to CommandLine.Process (:215637) is a separate route used by the in-game console. Practically the distinction matters because Submit prints a Cyan echo of the command and the console channel does not, so a detector keyed on that echo would miss real console input. Result: detail item 8 corrected and restamped, and expanded with the input-side mechanism, which had never been recorded: the thread reads System.Console.ReadKey() against the Win32 console input buffer rather than any stdin stream, Enter marshals the assembled line onto the Unity thread through a locked queue, and _inputThread.Start() is reached only through Ready() under the same !CustomLogFile guard that gates construction. This page had already recorded the -logFile gate correctly since 2026-07-27 (items 8 and 9), and that half is upheld unchanged; it was GameSystems/DedicatedServerSettings.md that lacked it. See that page's 2026-08-15 entry for the paired live arms.
  • 2026-07-29: conflict on "what puts BepInEx plugin log lines into Player.log". Previous claim (summary table and detail item 1): "BepInEx mod logs mirrored via StationeersLaunchPad". New finding: the route is BepInEx's own BepInEx.Logging.UnityLogListener, which formats with [{0,-7}:{1,10}] {2} and calls Unity's native UnityEngine.UnityLogWriter.WriteStringToUnityLog; StationeersLaunchPad is not involved. Fresh validator verdict: B is correct, quoting the assembly-qualified string UnityEngine.UnityLogWriter, UnityEngine.CoreModule from the #US heap of BepInEx\core\BepInEx.dll, with UnityLogListener, UnityLogWriter, WriteStringToUnityLog, WriteFromUnityLog and the namespace BepInEx.Logging in the same DLL's #Strings heap, and both target methods present in UnityEngine.CoreModule.dll. The decisive evidence is ordering: in a live Player.log, [Message: BepInEx] BepInEx 5.4.23.5 is line 19 and plugin ManualLogSource output ([Info :ClientDriver]) is at lines 39-43, while [Info : BepInEx] Loading [StationeersLaunchPad 0.5.0] is line 44 and StationeersLaunchPad's own first line is 52. A component not yet loaded cannot be mirroring. The two competing routes are both off at default ([Logging.Console] Enabled = false, [Logging] LogConsoleToUnityLog = false), which forces the conclusion. The original observation was always correct; only the actor was misattributed, so the console double-print rule in detail item 2 is unaffected and is in fact strengthened, since the native writer bypasses Debug.unityLogger entirely and therefore cannot re-enter the console bridge at any severity. Result: summary table row and detail item 1 corrected; new detail item 3 added on the reverse bridge, recording that [Logging.Disk] WriteUnityLog defaults to false and so keeps Debug.Log* out of LogOutput.log, plus the LogMessage lowercasing trap and the UNITY_ASSERTIONS stripping trap; downstream detail items renumbered. This closes the Open Question opened earlier the same day. BepInEx assemblies were NOT decompiled (repo rule); see the remaining Open Questions for what that leaves unread.
  • 2026-06-24: Page created from exhaustive search of Assembly-CSharp.decompiled.cs and 0Harmony.decompiled.cs against 0.2.6228.27061. Enumerated all StreamWriter, File.AppendText, File.Create, Application.logMessageReceived handlers, and console classes. Harmony logs verified from FileLog and FileWriter classes. Dedicated server logging verified from RocketSystemConsole and batch-mode initialization paths.
  • 2026-07-27: re-verified and restamped against 0.2.6403.27689 during a repo-wide console-output audit; all Assembly-CSharp line citations updated (they were from the 0.2.6228.27061 decompile, whose folder no longer exists), Harmony citations re-checked and the FileLog class line corrected from 8534 to 8529. Two corrections and one addition. Correction 1, the Player.log writer. The summary table credited Application.logMessageReceivedThreaded as the writer of Player.log. It is not a writer at all; it is Unity's notification event, and Unity's player runtime writes Player.log internally regardless of subscribers. The row now names the Unity runtime. Correction 2, what the subscription is for. Old detail item 1 listed "Subscriber: Application.logMessageReceivedThreaded (ConsoleWindow._Init line 206182)" under Player.log, which conflated a consumer with a sink. That subscription is now its own section (detail item 2) describing what it actually does: pull LogType.Error and LogType.Exception INTO the in-game console buffer. No fresh validator was needed for either, because this page's claim that the subscription exists was the correct half of a conflict with InGameConsoleOutput.md and was upheld; see that page's 2026-07-27 Verification History entry for the binding verdict, which cites this page's original line numbers as the evidence that the subscription predates this game version. Addition: an in-game console buffer row in the summary table, the -logFile interaction with the log bridge, the <color= drop on the non--logFile batch path, and the note that PrintBlock / PrintSegmentedBlock produce no dedicated-server output.

Open questions

  • Which WriteStringToUnityLog overload binds at runtime. Both WriteStringToUnityLog and WriteStringToUnityLogImpl appear as name strings in BepInEx.dll and both methods exist in UnityEngine.CoreModule.dll. The fallback chain that implied is now read in .work/decomp/0.2.6428.27798/BepInEx.decompiled.cs: BepInEx.dll carries its own internal sealed class UnityEngine.UnityLogWriter declaring both names as [MethodImpl(MethodImplOptions.InternalCall)] extern (:50-57), and the UnityLogListener static constructor (:3204-3236) calls each public static method of that stub with "" in the order reflection returns them (WriteStringToUnityLogImpl is declared first), binds the first that does not throw, and only if both throw reflects WriteStringToUnityLogImpl out of UnityEngine.UnityLogWriter, UnityEngine.CoreModule; if that fails too it logs Unable to start Unity log writer. So the answer is whichever internal call Unity 2022.3.62f3 registers, which was not determined. BepInEx's own changelog carries (37f0a9aa) [Soggy_Pancake] v5: Fix log writer errors for Unity 6 (#1264), consistent with that chain. This is a curiosity, not a blocker: the observable behaviour is identical either way.