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(anILogListenerinBepInEx.dll, alongsideConsoleLogListenerandDiskLogListener). 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 internalUnityEngine.UnityLogWriter.WriteStringToUnityLog, a native binding exported byUnityEngine.CoreModule.dll(Runtime/Export/Logging/UnityLogWriter.bindings.h). That call writes the string straight into the player log; it does not route throughDebug.unityLogger, so it raises nologMessageReceivedThreadedevent, 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 inPlayer.logbefore StationeersLaunchPad's assembly is loaded, andStationeersLaunchPad.decompiled.cscontains no reference toBepInEx.Loggingat 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
CustomLogFileis true (Assembly-CSharp:221915,CommandLineArgs?.Contains("-logFile") ?? false), which breaks a recursion loop: under batch +CustomLogFile,ConsoleWindow.Printitself callsUnityEngine.Debug.LogError/LogWarning/Log. - The non-threaded
Application.logMessageReceivedis 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:
LogMessagelowercases the message before printing it (logStr.ToLower(),Assembly-CSharp:222273), so aDebug.LogErrorshows the player mangled casing for mod names, device names, and IC10 tokens.Debug.LogAssertionandDebug.Assertare[Conditional("UNITY_ASSERTIONS")], evaluated at the caller's compile time. A mod whose.csprojdoes not defineUNITY_ASSERTIONShas the call removed by its own compiler and the message reaches nothing at all. Across the whole game assembly there are 49Debug.Log(, 36Debug.LogWarning(, 97Debug.LogError(and 35Debug.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%\Documentson 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, orlogfollowed 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 isTime + ": " + Text(Timeis stampedHH:mm:sswhen the line is set), followed by any continuation lines. An empty buffer printsNo console text to export.and writes nothing. - Implementation: LogCommand class (Assembly-CSharp:99884), dispatching
LogToFile(args).Forget()at :99900 intoprivate 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 examplePlayerLog.txt_2026-09-21_14-05-33.log. The default isstring.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}", "PlayerLog.txt", DateTime.Now), fromDEFAULT_LOG_NAME = "PlayerLog.txt", and the latertext.Replace(".log", "")strips only.log, so the.txtstays 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.
Executereceives the arguments without the command word, and the naming loop isfor (int i = 1; i < lineSplit.Length; i++)callingCommandBase.Get(lineSplit, 1, "logname", out string result)with the fixed index 1 on every pass. Solog mylogignoresmylogand writes the default name,log a bwritesb.log, andlog a b cwritesb_b.log. Quoting does not help:CmdLineParser.SplitCommandLinekeeps"my log"together as one argument, which is ignored when it is the only one;log x "my log"writesmy_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)andFlushBuffer()append to the file every time they are called. OnlyDebug(string)checksHarmony.DEBUG(line 8659), and nothing in 0Harmony callsDebug.Harmony.DEBUGdoes not switch this file on; it switches onHarmonyFileLog(item 7). - Who calls it: inside 0Harmony, only the
DebugLoggertranspiler helper (lines 4843-4851). It inserts aFileLog.Logcall 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 callingFileLogdirectly. The doc comments on[HarmonyDebug]and onPatch.debugand its constructor parameters still say that output goes toFileLogon the Desktop (lines 2113, 3675, 3720, 3743, 3768); no code in this build does that. The debug flag is handed toHarmonyLib.Tools.LoggerasforcePropagationinstead (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), andFileWriterPathdefaults 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 anyTextWritertoHarmonyFileLog.Writer, and then no file is created. - Activation:
HarmonyFileLog.Enabled = true(not default;BepInEx.dllnever sets it). TheHarmony(string id)constructor also sets it whenHarmony.DEBUGis true (lines 2577-2580), and first setsHarmony.DEBUGfrom aHARMONY_DEBUGenvironment variable when one is present (1ortrueturn it on,falseturns it off; lines 2567-2572).Harmony.DEBUGis[Obsolete("Use HarmonyFileLog.Enabled instead")](line 2545) and is turned intoEnabledonly inside that constructor, so setting it takes effect at the nextnew Harmony(...). - Content: messages raised through
HarmonyLib.Tools.Logger: only those whose channel passesLogger.ChannelFilter, plus those a patch's debug flag ([HarmonyDebug]) force-propagates. 0Harmony never assignsChannelFilter. Under BepInEx it is set from[Harmony.Logger] LogChannelsinBepInEx.cfg(defaultWarn, Error) by BepInEx'sHarmonyLogSource, which also forwards those channels into the BepInEx log as sourceHarmonyX, so HarmonyX warnings and errors already reachLogOutput.logandPlayer.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.Toolsopens at line 9286). No class namedFileWriterexists in the assembly; this page used that name until 2026-09-21, taken from theFileWriterPathproperty. - 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 toLogger.MessageReceived(line 9342) - Format:
[{e.LogChannel}] {e.Message}, flushed after every line (line 9352) - Duplicate lines: the
Enabledsetter subscribes with+=every time it is set to true and never checks for an existing subscription. WithHarmony.DEBUGon, everyHarmonyinstance 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"unlessApplication.platformisLinuxServerorWindowsServer. - 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 throughConsoleWindow.Submit, which this page claimed until 2026-08-15;Submitis 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:
PrintBlockandPrintSegmentedBlockskip their entire body when IsBatchMode (Assembly-CSharp:223036), so they produce NO dedicated-server output. Only the plainPrinthas 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 onSystem.Console.CursorVisibleandSystem.Console.KeyAvailableand callsSystem.Console.ReadKey()(:107335, :107339). Those are the Win32 console input buffer, reached throughPeekConsoleInput/ReadConsoleInput. An anonymous pipe on the child's stdin is a different object and is never read.ReadKeyandKeyAvailableeach occur exactly once in the whole assembly, both here.EntertriggersOnEnter(), which strips the"> "prefix, trims, and enqueues underlock (_inputQueue). A separateTick()loop drains that queue only when running on the Unity thread, then raisesOnInputReceived. 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 fromConsoleWindow.WaitForGameToBeReadyThenOverrideConsoleInput()(:215503) afterGameManager.IsInitializedplus a 1 s delay, and under the same!CustomLogFileguard. With-logFileon the launch line there is no console object, no input thread and no handler.- Measured 2026-08-15 on 0.2.6428.27798,
-logFileREMOVED so the console was fully up (stdout carried***Stationeers - 0.2.6428.27798***thenReady):settings ServerMaxPlayers 9written 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.dlllives". Three conflicts, each put to its own fresh validator using theResearch/WORKFLOW.mdtemplate with the two source labels removed, so no validator could tell which claim was on the page. (1)FileLogandHarmony.DEBUG. Previous claim (summary table row "Harmony debug log", detail item 6):HarmonyLib.FileLogwritesDesktop\harmony.log.txt"Only if Harmony.DEBUG = true", holding "Low-level transpiler and patch-application traces". New finding:FileLoghas no switch,LogandFlushBufferwrite whenever they are called, onlyFileLog.Debug(string)checksHarmony.DEBUG, and the only caller inside 0Harmony is theDebugLoggertranspiler helper;Harmony.DEBUGis[Obsolete("Use HarmonyFileLog.Enabled instead")], and theHarmony(string id)constructor turns it, orHARMONY_DEBUG, intoHarmonyFileLog.Enabled = true, which writesHarmonyLog.txt. Fresh validator verdict: B is correct, quotingFileLog.LogandFileLog.Debug(if (Harmony.DEBUG) { Log(str); }, :8646-8663) and the constructor'sif (DEBUG) { HarmonyFileLog.Enabled = true; }(:2577-2580). It added that nothing in the assembly callsFileLog.Debug, that theDebugLoggercall is written into the patched method and so logs each time that method runs, thatReset()deletes the Desktop file even whenHARMONY_LOG_FILEpoints elsewhere, and that the doc comments on[HarmonyDebug]andPatch.debugpromising DesktopFileLogoutput (:2113, :3675, :3720, :3743, :3768) describe no code in this build, which is the likely origin of the old claim. (2) TheHarmonyLog.txtwriter. Previous claim (row "Harmony FileWriter", detail item 7): classHarmonyLib.FileWriter, "Only if FileWriter.Enabled = true", path overridable throughFileWriter.FileWriterPath. New finding: the class isHarmonyLib.Tools.HarmonyFileLog(:9291), noFileWriterclass exists,FileWriterPathdefaults to"HarmonyLog.txt"(:9332), andHarmony.DEBUGandHARMONY_DEBUGalso switch it on. Fresh validator verdict: B is correct, quotingnamespace HarmonyLib.Toolsthroughpublic static class HarmonyFileLog(:9286-9291),public static string FileWriterPath { get; set; } = "HarmonyLog.txt";(:9332) andWriter = new StreamWriter(File.Create(Path.GetFullPath(FileWriterPath)));(:9340); a whole-word search of the decompile finds noFileWriter. It added that no file is created when a caller has already setWriter, thatFile.Createtruncates, that nothing in 0Harmony assignsLogger.ChannelFilter, and, from the code rather than a run, that eachEnabled = trueadds anotherMessageReceivedhandler. (3) Where the DLL is. Previous claim (frontmatter sources):$(StationeersPath)\rocketstation_Data\Managed\0Harmony.dll. New finding: the only0Harmony.dllin the install isBepInEx\core\0Harmony.dll. Fresh validator verdict: B is correct, quoting a recursive search of the install for0harmony*that returnsBepInEx/core/0Harmony.dll,BepInEx/core/0Harmony.xmlandBepInEx/core/0Harmony20.dlland nothing else;rocketstation_Data\Managed\holds 146 files, none with "harmony" in the name.BepInEx\core\0Harmony.dllis assembly0Harmony2.9.0.0, product HarmonyX, with an attribute block matching the decompile's;0Harmony20.dllis a different assembly (0Harmony202.0.0.0, productHarmonyX2Interop). The dedicated server install and the rig's client installs also keep it only underBepInEx\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 namedFileWriterand cited:9332-9355, the same range it later cited against 0.2.6403.27689 and the range that today holdsFileWriterPaththrough the end ofHarmonyFileLog; 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'sHarmonyLogSourcesets 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 theUnityLogListenerbody 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) isif (!(eventArgs.Source is UnityLogSource) && (LogConsoleToUnity.Value || eventArgs.Source.SourceName != "Console")) { WriteStringToUnityLog?.Invoke(eventArgs.ToStringLine()); }, whereToStringLine()isToString()plus a newline andToString()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 fromUnityLogSource, which would loop, and, unless[Logging] LogConsoleToUnityLogis on, events from theConsolesource. The current.work/decomp/0.2.6428.27798/StationeersLaunchPad.decompiled.cs(0.5.1.0) still has no reference toBepInEx.Logging,ILogListener,LogEventorManualLogSource. The "which overload binds" question stays open, rewritten with the probe order the static constructor uses. - 2026-09-21: conflict on "where the
logconsole command writes its export, and what it names the file". Two separate conflicts, each put to its own fresh validator using theResearch/WORKFLOW.mdtemplate 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.LocalDataisEnvironment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + _baseFolderwith_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 readSystem.Environment.GetFolderPathin the game'smscorlib.dllthrough an in-memory decompile (no file written): on Windows it returnsGetWindowsFolderPath((int)folder), an internal call, and only the non-Windows branch mapsMyDocumentsto the home directory, the one case where the old path shape would hold. It parsed the import table ofmono-2.0-bdwgc.dll(SHGetFolderPathWandSHGetKnownFolderPathfromSHELL32.dll), and on the development machine foundDocuments\My Games\Stationeers\present with asetting.xmlwritten 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, andPlayerLog_*.login the summary table): default namePlayerLog_YYYY-MM-DD_HH-mm-ss.log, andlog <customname>names the file. New finding: the default isPlayerLog.txt_yyyy-MM-dd_HH-mm-ss.log, and a single argument is ignored because the naming loop starts at index 1 and always readslineSplit[1]. Fresh validator verdict: B is correct, quoting theLogToFilenaming block (:99917-99933) and tracing the console line throughConsoleWindow.Submit,CommandLine.Process(string),CommandLine.Process(string[], bool)andExecuteCommandtoExecute(args)with the command word removed; an exact-match search of the installedAssembly-CSharp.dllstrings found noPlayerLog_at all, onlyPlayerLog.txt. Whether the old name was ever right is not established: theLogCommandlines 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_baseFolderline 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.277980Harmonydecompile (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.dllat 0.2.6428.27798: the console subscribes directly,_systemConsoleInput.OnInputReceived += CommandLine.Process;(:215509), and never touchesSubmit. Verdict: the new finding is correct; the tokenCommandLine.Processoccurs exactly four times in the assembly and :215509 is one of them, whileConsoleWindow.Submit's own call toCommandLine.Process(:215637) is a separate route used by the in-game console. Practically the distinction matters becauseSubmitprints 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 readsSystem.Console.ReadKey()against the Win32 console input buffer rather than any stdin stream,Entermarshals the assembled line onto the Unity thread through a locked queue, and_inputThread.Start()is reached only throughReady()under the same!CustomLogFileguard that gates construction. This page had already recorded the-logFilegate correctly since 2026-07-27 (items 8 and 9), and that half is upheld unchanged; it wasGameSystems/DedicatedServerSettings.mdthat 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 ownBepInEx.Logging.UnityLogListener, which formats with[{0,-7}:{1,10}] {2}and calls Unity's nativeUnityEngine.UnityLogWriter.WriteStringToUnityLog; StationeersLaunchPad is not involved. Fresh validator verdict: B is correct, quoting the assembly-qualified stringUnityEngine.UnityLogWriter, UnityEngine.CoreModulefrom the#USheap ofBepInEx\core\BepInEx.dll, withUnityLogListener,UnityLogWriter,WriteStringToUnityLog,WriteFromUnityLogand the namespaceBepInEx.Loggingin the same DLL's#Stringsheap, and both target methods present inUnityEngine.CoreModule.dll. The decisive evidence is ordering: in a livePlayer.log,[Message: BepInEx] BepInEx 5.4.23.5is line 19 and pluginManualLogSourceoutput ([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 bypassesDebug.unityLoggerentirely 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] WriteUnityLogdefaults to false and so keepsDebug.Log*out ofLogOutput.log, plus theLogMessagelowercasing trap and theUNITY_ASSERTIONSstripping 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.logMessageReceivedThreadedas 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: pullLogType.ErrorandLogType.ExceptionINTO 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 withInGameConsoleOutput.mdand 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-logFileinteraction with the log bridge, the<color=drop on the non--logFilebatch path, and the note thatPrintBlock/PrintSegmentedBlockproduce no dedicated-server output.
Open questions¶
- Which
WriteStringToUnityLogoverload binds at runtime. BothWriteStringToUnityLogandWriteStringToUnityLogImplappear as name strings inBepInEx.dlland both methods exist inUnityEngine.CoreModule.dll. The fallback chain that implied is now read in.work/decomp/0.2.6428.27798/BepInEx.decompiled.cs:BepInEx.dllcarries its owninternal sealed class UnityEngine.UnityLogWriterdeclaring both names as[MethodImpl(MethodImplOptions.InternalCall)] extern(:50-57), and theUnityLogListenerstatic constructor (:3204-3236) calls each public static method of that stub with""in the order reflection returns them (WriteStringToUnityLogImplis declared first), binds the first that does not throw, and only if both throw reflectsWriteStringToUnityLogImplout ofUnityEngine.UnityLogWriter, UnityEngine.CoreModule; if that fails too it logsUnable 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.