diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs
index 4bcbf5be..759e69ed 100644
--- a/MinecraftClient/Program.cs
+++ b/MinecraftClient/Program.cs
@@ -58,6 +58,17 @@ namespace MinecraftClient
// Setting this string to an empty string will disable Sentry
private const string SentryDSN = "";
+ ///
+ /// Snapshot of all state collected before the console backend is initialized.
+ /// Passed to once the backend is ready.
+ ///
+ internal sealed class StartupState
+ {
+ public Settings.ConfigLoadResult ConfigResult { get; init; }
+ public bool NewlyGenerated { get; init; }
+ public bool SentryEnabled { get; init; }
+ }
+
///
/// The main entry point of Minecraft Console Client
///
@@ -103,7 +114,6 @@ namespace MinecraftClient
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
});
- //Setup ConsoleIO
ConsoleIO.LogPrefix = "§8[MCC] ";
if (args.Length >= 1 && args[^1] == "BasicIO" || args.Length >= 1 && args[^1] == "BasicIO-NoColor")
{
@@ -115,133 +125,178 @@ namespace MinecraftClient
args = args.Where(o => !Object.ReferenceEquals(o, args[^1])).ToArray();
}
+ //Debug input ?
+ if (args.Length == 1 && args[0] == "--keyboard-debug")
+ {
+ if (!ConsoleIO.BasicIO)
+ {
+ ConsoleIO.Backend = new ClassicConsoleBackend();
+ ConsoleIO.Backend.Init();
+ }
+ ConsoleIO.WriteLine("Keyboard debug mode: Press any key to display info");
+ ConsoleIO.DebugReadInput();
+ }
+
+ // --- Load config as early as possible (no printing yet) ---
+ Settings.ConfigLoadResult configResult;
+ bool newlyGenerated = false;
+
+ if (args.Length >= 1 && File.Exists(args[0]) && Settings.ToLowerIfNeed(Path.GetExtension(args[0])) == ".ini")
+ {
+ configResult = Settings.LoadFromFile(args[0]);
+ settingsIniPath = args[0];
+
+ List args_tmp = args.ToList();
+ args_tmp.RemoveAt(0);
+ args = args_tmp.ToArray();
+ }
+ else if (File.Exists("MinecraftClient.ini"))
+ {
+ configResult = Settings.LoadFromFile("MinecraftClient.ini");
+ }
+ else
+ {
+ configResult = new Settings.ConfigLoadResult { Success = true, NeedWriteDefault = true };
+ newlyGenerated = true;
+ }
+
+ if (configResult.NeedWriteDefault)
+ {
+ Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
+ WriteBackSettings(false);
+ }
+ else if (configResult.Success)
+ {
+ WriteBackSettings(true);
+ }
+
+ if (!Config.Main.Advanced.EnableSentry)
+ _sentrySdk?.Dispose();
+
+ var startupState = new StartupState
+ {
+ ConfigResult = configResult,
+ NewlyGenerated = newlyGenerated,
+ SentryEnabled = SentryDSN != string.Empty,
+ };
+
+ // --- Determine console mode and initialize backend ---
+ if (!ConsoleIO.BasicIO && Config.Console.General.ConsoleMode == ConsoleModeType.tui)
+ {
+ ConsoleIO.Backend?.Shutdown();
+ var tuiBackend = new Tui.TuiConsoleBackend();
+ ConsoleIO.Backend = tuiBackend;
+ tuiBackend.RunTuiMainLoop(args, startupState);
+ return;
+ }
+
+ // Classic mode: init backend, then print and process startup state.
if (!ConsoleIO.BasicIO)
{
ConsoleIO.Backend = new ClassicConsoleBackend();
ConsoleIO.Backend.Init();
}
- ConsoleIO.WriteLine($"Minecraft Console Client v{Version} - for MC {MCLowestVersion} to {MCHighestVersion} - Github.com/MCCTeam");
-
- //Build information to facilitate processing of bug reports
- if (BuildInfo is not null)
- ConsoleIO.WriteLineFormatted("§8" + BuildInfo);
-
- //Debug input ?
- if (args.Length == 1 && args[0] == "--keyboard-debug")
- {
- ConsoleIO.WriteLine("Keyboard debug mode: Press any key to display info");
- ConsoleIO.DebugReadInput();
- }
-
- //Process ini configuration file
- {
- bool loadSucceed, needWriteDefaultSetting, newlyGenerated = false;
- if (args.Length >= 1 && File.Exists(args[0]) && Settings.ToLowerIfNeed(Path.GetExtension(args[0])) == ".ini")
- {
- (loadSucceed, needWriteDefaultSetting) = Settings.LoadFromFile(args[0]);
- settingsIniPath = args[0];
-
- //remove ini configuration file from arguments array
- List args_tmp = args.ToList();
- args_tmp.RemoveAt(0);
- args = args_tmp.ToArray();
- }
- else if (File.Exists("MinecraftClient.ini"))
- {
- (loadSucceed, needWriteDefaultSetting) = Settings.LoadFromFile("MinecraftClient.ini");
- }
- else
- {
- loadSucceed = true;
- needWriteDefaultSetting = true;
- newlyGenerated = true;
- }
-
- if (needWriteDefaultSetting)
- {
- Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
- WriteBackSettings(false);
- if (newlyGenerated)
- ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_settings_generated);
- ConsoleIO.WriteLine(Translations.mcc_run_with_default_settings);
-
- // Only show the Sentry message if the DSN is not empty
- // as Sentry will not be initialized if the DSN is empty
- if (SentryDSN != string.Empty)
- {
- ConsoleIO.WriteLine(Translations.mcc_sentry_logging);
- }
- }
- else if (!loadSucceed)
- {
- ConsoleIO.Backend?.StopReadThread();
- string command = " ";
- while (command.Length > 0)
- {
- ConsoleIO.WriteLine(string.Empty);
- ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_invaild_config, Config.Main.Advanced.InternalCmdChar.ToLogString()));
- if (ConsoleIO.Backend is Tui.TuiConsoleBackend)
- ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_use_quit_to_exit, Config.Main.Advanced.InternalCmdChar.ToLogString()));
- else
- ConsoleIO.WriteLineFormatted(Translations.mcc_press_exit, acceptnewlines: true);
- command = ConsoleIO.ReadLine().Trim();
- if (command.Length > 0)
- {
- if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
- && command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
- command = command[1..];
-
- if (command.StartsWith("exit") || command.StartsWith("quit"))
- {
- return;
- }
- else if (command.StartsWith("new"))
- {
- Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
- WriteBackSettings(true);
- ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_gen_new_config, settingsIniPath));
- return;
- }
- }
- else
- {
- return;
- }
- }
- return;
- }
- else
- {
- //Load external translation file. Should be called AFTER settings loaded
- if (!Config.Main.Advanced.Language.StartsWith("en"))
- ConsoleIO.WriteLine(string.Format(Translations.mcc_help_us_translate, Settings.TranslationProjectUrl));
- WriteBackSettings(true); // format
- }
-
- if (!Config.Main.Advanced.EnableSentry)
- _sentrySdk?.Dispose();
- }
-
- // Switch to TUI mode if configured (must happen after config load)
- if (!ConsoleIO.BasicIO && Config.Console.General.ConsoleMode == ConsoleModeType.tui)
- {
- ConsoleIO.Backend?.Shutdown();
- var tuiBackend = new Tui.TuiConsoleBackend();
- ConsoleIO.Backend = tuiBackend;
- tuiBackend.RunTuiMainLoop(args);
+ if (!ProcessStartupState(startupState))
return;
- }
- ContinueAfterTuiInit(args);
+ RunStartupSequence(args);
}
///
- /// Continues MCC startup after console mode has been determined.
- /// Called directly from Main for classic/basic mode, or from a background
- /// thread for TUI mode (after the Avalonia UI loop has started).
+ /// Prints the application banner and processes the startup state collected before
+ /// the console backend was ready. Called once from classic mode or from TUI after
+ /// the view is initialized.
///
- internal static void ContinueAfterTuiInit(string[] args)
+ /// True if startup can continue; false if config load failed and user chose to exit.
+ internal static bool ProcessStartupState(StartupState state)
+ {
+ ConsoleIO.WriteLine($"Minecraft Console Client v{Version} - for MC {MCLowestVersion} to {MCHighestVersion} - Github.com/MCCTeam");
+ if (BuildInfo is not null)
+ ConsoleIO.WriteLineFormatted("§8" + BuildInfo);
+
+ var cfg = state.ConfigResult;
+
+ if (cfg.NeedWriteDefault)
+ {
+ if (cfg.IsLegacyUpgrade)
+ {
+ ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_use_new_config);
+ ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.mcc_backup_old_config, cfg.LegacyBackupPath));
+ }
+
+ if (state.NewlyGenerated)
+ ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_settings_generated);
+
+ ConsoleIO.WriteLine(Translations.mcc_run_with_default_settings);
+
+ if (state.SentryEnabled)
+ ConsoleIO.WriteLine(Translations.mcc_sentry_logging);
+ }
+ else if (!cfg.Success)
+ {
+ ConsoleIO.WriteLineFormatted("§c" + Translations.config_load_fail);
+ if (cfg.ErrorMessage is not null)
+ ConsoleIO.WriteLine(cfg.ErrorMessage);
+ HandleConfigLoadFailure();
+ return false;
+ }
+ else
+ {
+ if (!Config.Main.Advanced.Language.StartsWith("en"))
+ ConsoleIO.WriteLine(string.Format(Translations.mcc_help_us_translate, Settings.TranslationProjectUrl));
+ }
+
+ return true;
+ }
+
+ ///
+ /// Handles a failed config load by prompting the user to fix or regenerate the config file.
+ ///
+ internal static void HandleConfigLoadFailure()
+ {
+ ConsoleIO.Backend?.StopReadThread();
+ string command = " ";
+ while (command.Length > 0)
+ {
+ ConsoleIO.WriteLine(string.Empty);
+ ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_invaild_config, Config.Main.Advanced.InternalCmdChar.ToLogString()));
+ if (ConsoleIO.Backend is Tui.TuiConsoleBackend)
+ ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_use_quit_to_exit, Config.Main.Advanced.InternalCmdChar.ToLogString()));
+ else
+ ConsoleIO.WriteLineFormatted(Translations.mcc_press_exit, acceptnewlines: true);
+ command = ConsoleIO.ReadLine().Trim();
+ if (command.Length > 0)
+ {
+ if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
+ && command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
+ command = command[1..];
+
+ if (command.StartsWith("exit") || command.StartsWith("quit"))
+ {
+ return;
+ }
+ else if (command.StartsWith("new"))
+ {
+ Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
+ WriteBackSettings(true);
+ ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_gen_new_config, settingsIniPath));
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Runs the main startup sequence: CLI argument processing, auth, and connection.
+ /// Called from Main() for classic/basic mode, or from TuiConsoleBackend on a
+ /// background thread after the Avalonia UI loop has started.
+ ///
+ internal static void RunStartupSequence(string[] args)
{
//Other command-line arguments
if (args.Length >= 1)
@@ -732,7 +787,8 @@ namespace MinecraftClient
///
public static void ReloadSettings(bool keepAccountAndServerSettings = false)
{
- if (Settings.LoadFromFile(settingsIniPath, keepAccountAndServerSettings).Item1)
+ var result = Settings.LoadFromFile(settingsIniPath, keepAccountAndServerSettings);
+ if (result.Success)
ConsoleIO.WriteLine(string.Format(Translations.config_load, settingsIniPath));
}
diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs
index 79e314d0..a3368c8e 100644
--- a/MinecraftClient/Settings.cs
+++ b/MinecraftClient/Settings.cs
@@ -136,7 +136,22 @@ namespace MinecraftClient
}
- public static Tuple LoadFromFile(string filepath, bool keepAccountAndServerSettings = false)
+ ///
+ /// Structured result returned by .
+ ///
+ public readonly struct ConfigLoadResult
+ {
+ public bool Success { get; init; }
+ public bool NeedWriteDefault { get; init; }
+ /// True when a pre-TOML legacy config was detected, backed up, and a fresh default is needed.
+ public bool IsLegacyUpgrade { get; init; }
+ /// Non-null when the load failed due to a parse/IO error (not a legacy upgrade).
+ public string? ErrorMessage { get; init; }
+ /// Path where the old config was backed up (legacy upgrade case).
+ public string? LegacyBackupPath { get; init; }
+ }
+
+ public static ConfigLoadResult LoadFromFile(string filepath, bool keepAccountAndServerSettings = false)
{
bool keepAccountSettings = InternalConfig.KeepAccountSettings;
bool keepServerSettings = InternalConfig.KeepServerSettings;
@@ -157,21 +172,27 @@ namespace MinecraftClient
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
try
{
- // The old configuration file has been backed up as A.
string configString = File.ReadAllText(filepath);
if (configString.Contains("Some settings missing here after an upgrade?"))
{
string newFilePath = Path.ChangeExtension(filepath, ".old.ini");
File.Copy(filepath, newFilePath, true);
- ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_use_new_config);
- ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.mcc_backup_old_config, newFilePath));
- return new(false, true);
+ return new ConfigLoadResult
+ {
+ Success = false,
+ NeedWriteDefault = true,
+ IsLegacyUpgrade = true,
+ LegacyBackupPath = newFilePath
+ };
}
}
catch { }
- ConsoleIO.WriteLineFormatted("§c" + Translations.config_load_fail);
- ConsoleIO.WriteLine(ex.GetFullMessage());
- return new(false, false);
+ return new ConfigLoadResult
+ {
+ Success = false,
+ NeedWriteDefault = false,
+ ErrorMessage = ex.GetFullMessage()
+ };
}
finally
{
@@ -180,7 +201,7 @@ namespace MinecraftClient
if (!keepServerSettings)
InternalConfig.KeepServerSettings = false;
}
- return new(true, false);
+ return new ConfigLoadResult { Success = true, NeedWriteDefault = false };
}
public static void WriteToFile(string filepath, bool backupOldFile)
diff --git a/MinecraftClient/Tui/TuiConsoleBackend.cs b/MinecraftClient/Tui/TuiConsoleBackend.cs
index 64d5af86..bee59751 100644
--- a/MinecraftClient/Tui/TuiConsoleBackend.cs
+++ b/MinecraftClient/Tui/TuiConsoleBackend.cs
@@ -25,14 +25,18 @@ namespace MinecraftClient.Tui
internal static TuiConsoleBackend? Instance { get; private set; }
+ private Program.StartupState? _pendingStartupState;
+ private readonly ManualResetEventSlim _viewReady = new(false);
+
///
/// Initializes the Avalonia app and starts the main UI loop.
/// This blocks the calling thread until the TUI exits.
/// Before blocking, it starts MCC's remaining initialization on a background thread.
///
- public void RunTuiMainLoop(string[] args)
+ internal void RunTuiMainLoop(string[] args, Program.StartupState startupState)
{
Instance = this;
+ _pendingStartupState = startupState;
AppDomain.CurrentDomain.ProcessExit += (_, _) => RestoreTerminalState();
@@ -46,7 +50,7 @@ namespace MinecraftClient.Tui
new Thread(() =>
{
- Thread.Sleep(500);
+ _viewReady.Wait();
ContinueMccStartup(args);
})
{ Name = "MCC-Main", IsBackground = true }.Start();
@@ -113,7 +117,15 @@ namespace MinecraftClient.Tui
{
try
{
- Program.ContinueAfterTuiInit(args);
+ var instance = Instance;
+ if (instance?._pendingStartupState is { } state)
+ {
+ instance._pendingStartupState = null;
+ if (!Program.ProcessStartupState(state))
+ return;
+ }
+
+ Program.RunStartupSequence(args);
}
catch (Exception ex)
{
@@ -124,6 +136,7 @@ namespace MinecraftClient.Tui
internal void SetView(MainTuiView view)
{
_view = view;
+ _viewReady.Set();
}
internal MainTuiView? GetView() => _view;