mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Problems:
1) The AutoRelog ChatBot stopped trying to log in after the 3rd attempt. 2) The AutoRelog ChatBot stopped trying to log in after the internet connection was interrupted. MCC froze. 2 problems have been solved.
This commit is contained in:
parent
142fb107db
commit
9e271a2f06
8 changed files with 668 additions and 327 deletions
|
|
@ -131,7 +131,7 @@ namespace MinecraftClient.ChatBots
|
|||
else if (CanReconnect())
|
||||
{
|
||||
message = GetVerbatim(message);
|
||||
string comp = message.ToLower();
|
||||
string comp = message.ToLowerInvariant();
|
||||
|
||||
LogDebugToConsole(string.Format(Translations.bot_autoRelog_disconnect_msg, message));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
namespace MinecraftClient
|
||||
{
|
||||
|
|
@ -109,9 +110,32 @@ namespace MinecraftClient
|
|||
|
||||
public void StopReadThread()
|
||||
{
|
||||
ConsoleInteractive.ConsoleReader.StopReadThread();
|
||||
ConsoleInteractive.ConsoleReader.MessageReceived -= ForwardMessage;
|
||||
ConsoleInteractive.ConsoleReader.OnInputChange -= ForwardInputChange;
|
||||
Thread stopThread = new(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ConsoleInteractive.ConsoleReader.StopReadThread();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort shutdown; do not block the caller thread during reconnect/restart.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ConsoleInteractive.ConsoleReader.MessageReceived -= ForwardMessage;
|
||||
ConsoleInteractive.ConsoleReader.OnInputChange -= ForwardInputChange;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore detach failures.
|
||||
}
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "ClassicConsoleBackend.StopReadThread"
|
||||
};
|
||||
stopThread.Start();
|
||||
}
|
||||
|
||||
public string RequestImmediateInput()
|
||||
|
|
|
|||
|
|
@ -92,6 +92,29 @@ namespace MinecraftClient
|
|||
return Backend.RequestImmediateInput();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a line from the standard input but return quickly if the backend stalls.
|
||||
/// This is used by the offline prompt so disconnect/reconnect handling cannot hang forever.
|
||||
/// </summary>
|
||||
public static string ReadLineWithTimeout(TimeSpan timeout)
|
||||
{
|
||||
if (BasicIO)
|
||||
return Console.ReadLine() ?? String.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var inputTask = Task.Run(() => Backend.RequestImmediateInput());
|
||||
if (inputTask.Wait(timeout))
|
||||
return inputTask.Result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore backend stalls and fall back to an empty input so the reconnect flow can continue.
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug routine: print all keys pressed in the console
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ namespace MinecraftClient
|
|||
Tuple<Thread, CancellationTokenSource>? timeoutdetector = null;
|
||||
private Thread? basicIOReadThread;
|
||||
private int transferInProgress = 0;
|
||||
private int disconnectInProgress = 0;
|
||||
private bool consoleReadThreadOwned = false;
|
||||
private bool consoleHandlersAttached = false;
|
||||
|
||||
|
|
@ -568,6 +569,7 @@ namespace MinecraftClient
|
|||
if (ConsoleIO.BasicIO || ConsoleIO.Backend is null)
|
||||
{
|
||||
cmdprompt?.Cancel();
|
||||
cmdprompt = null;
|
||||
basicIOReadThread = null;
|
||||
consoleReadThreadOwned = false;
|
||||
consoleHandlersAttached = false;
|
||||
|
|
@ -576,15 +578,36 @@ namespace MinecraftClient
|
|||
|
||||
if (consoleHandlersAttached)
|
||||
{
|
||||
ConsoleIO.Backend.MessageReceived -= ConsoleReaderOnMessageReceived;
|
||||
ConsoleIO.Backend.OnInputChange -= ConsoleIO.AutocompleteHandler;
|
||||
try
|
||||
{
|
||||
ConsoleIO.Backend.MessageReceived -= ConsoleReaderOnMessageReceived;
|
||||
ConsoleIO.Backend.OnInputChange -= ConsoleIO.AutocompleteHandler;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort; a stale subscription should not block teardown.
|
||||
}
|
||||
consoleHandlersAttached = false;
|
||||
}
|
||||
|
||||
if (consoleReadThreadOwned)
|
||||
{
|
||||
try
|
||||
{
|
||||
ConsoleIO.Backend.StopReadThread();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort; a stalled read thread should not block disconnect handling.
|
||||
}
|
||||
finally
|
||||
{
|
||||
consoleReadThreadOwned = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsoleIO.Backend.StopReadThread();
|
||||
consoleReadThreadOwned = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -866,36 +889,60 @@ namespace MinecraftClient
|
|||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
instance = null;
|
||||
if (Interlocked.Exchange(ref disconnectInProgress, 1) != 0)
|
||||
return;
|
||||
|
||||
DispatchBotEvent(bot => bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, ""));
|
||||
|
||||
foreach (ChatBot bot in bots.Where(bot => bot.ScriptOwnerKey is not null).ToList())
|
||||
BotUnLoad(bot);
|
||||
|
||||
botsOnHold.Clear();
|
||||
botsOnHold.AddRange(bots.Where(bot => bot.ScriptOwnerKey is null));
|
||||
|
||||
if (handler is not null)
|
||||
try
|
||||
{
|
||||
handler.Disconnect();
|
||||
handler.Dispose();
|
||||
}
|
||||
try
|
||||
{
|
||||
StopConsoleSession();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort; disconnect should not be blocked by console teardown failures.
|
||||
}
|
||||
|
||||
if (cmdprompt is not null)
|
||||
bool clearSharedState = ReferenceEquals(instance, this);
|
||||
if (clearSharedState)
|
||||
instance = null;
|
||||
|
||||
if (ReferenceEquals(CmdResult.currentHandler, this))
|
||||
CmdResult.currentHandler = null;
|
||||
|
||||
DispatchBotEvent(bot => bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, ""));
|
||||
|
||||
foreach (ChatBot bot in bots.Where(bot => bot.ScriptOwnerKey is not null).ToList())
|
||||
BotUnLoad(bot);
|
||||
|
||||
botsOnHold.Clear();
|
||||
botsOnHold.AddRange(bots.Where(bot => bot.ScriptOwnerKey is null));
|
||||
|
||||
if (handler is not null)
|
||||
{
|
||||
handler.Disconnect();
|
||||
handler.Dispose();
|
||||
}
|
||||
|
||||
if (cmdprompt is not null)
|
||||
{
|
||||
cmdprompt.Cancel();
|
||||
cmdprompt = null;
|
||||
}
|
||||
|
||||
if (timeoutdetector is not null)
|
||||
{
|
||||
timeoutdetector.Item2.Cancel();
|
||||
timeoutdetector = null;
|
||||
}
|
||||
|
||||
if (client is not null)
|
||||
client.Close();
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmdprompt.Cancel();
|
||||
cmdprompt = null;
|
||||
Interlocked.Exchange(ref disconnectInProgress, 0);
|
||||
}
|
||||
|
||||
if (timeoutdetector is not null)
|
||||
{
|
||||
timeoutdetector.Item2.Cancel();
|
||||
timeoutdetector = null;
|
||||
}
|
||||
|
||||
if (client is not null)
|
||||
client.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -903,71 +950,95 @@ namespace MinecraftClient
|
|||
/// </summary>
|
||||
public void OnConnectionLost(ChatBot.DisconnectReason reason, string message)
|
||||
{
|
||||
instance = null;
|
||||
if (Interlocked.Exchange(ref disconnectInProgress, 1) != 0)
|
||||
return;
|
||||
|
||||
ConsoleIO.CancelAutocomplete();
|
||||
|
||||
handler.Dispose();
|
||||
|
||||
world.Clear();
|
||||
ClearKnownSigns();
|
||||
|
||||
if (timeoutdetector is not null)
|
||||
{
|
||||
if (timeoutdetector is not null && Thread.CurrentThread != timeoutdetector.Item1)
|
||||
timeoutdetector.Item2.Cancel();
|
||||
timeoutdetector = null;
|
||||
}
|
||||
|
||||
bool will_restart = false;
|
||||
|
||||
switch (reason)
|
||||
{
|
||||
case ChatBot.DisconnectReason.ConnectionLost:
|
||||
message = Translations.mcc_disconnect_lost;
|
||||
Log.Info(message);
|
||||
break;
|
||||
|
||||
case ChatBot.DisconnectReason.InGameKick:
|
||||
Log.Info(Translations.mcc_disconnect_server);
|
||||
Log.Info(message);
|
||||
break;
|
||||
|
||||
case ChatBot.DisconnectReason.LoginRejected:
|
||||
Log.Info(Translations.mcc_disconnect_login);
|
||||
Log.Info(message);
|
||||
break;
|
||||
|
||||
case ChatBot.DisconnectReason.UserLogout:
|
||||
throw new InvalidOperationException(Translations.exception_user_logout);
|
||||
}
|
||||
|
||||
//Process AutoRelog last to make sure other bots can perform their cleanup tasks first (issue #1517)
|
||||
List<ChatBot> onDisconnectBotList = bots.Where(bot => bot is not AutoRelog).ToList();
|
||||
onDisconnectBotList.AddRange(bots.Where(bot => bot is AutoRelog));
|
||||
|
||||
foreach (ChatBot bot in onDisconnectBotList)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
will_restart |= bot.OnDisconnect(reason, message);
|
||||
StopConsoleSession();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
if (e is not ThreadAbortException)
|
||||
// Best-effort; disconnect should not be blocked by console teardown failures.
|
||||
}
|
||||
|
||||
bool clearSharedState = ReferenceEquals(instance, this);
|
||||
if (clearSharedState)
|
||||
instance = null;
|
||||
|
||||
if (ReferenceEquals(CmdResult.currentHandler, this))
|
||||
CmdResult.currentHandler = null;
|
||||
|
||||
ConsoleIO.CancelAutocomplete();
|
||||
|
||||
handler.Dispose();
|
||||
|
||||
world.Clear();
|
||||
ClearKnownSigns();
|
||||
|
||||
if (timeoutdetector is not null)
|
||||
{
|
||||
if (timeoutdetector is not null && Thread.CurrentThread != timeoutdetector.Item1)
|
||||
timeoutdetector.Item2.Cancel();
|
||||
timeoutdetector = null;
|
||||
}
|
||||
|
||||
bool will_restart = false;
|
||||
|
||||
switch (reason)
|
||||
{
|
||||
case ChatBot.DisconnectReason.ConnectionLost:
|
||||
message = Translations.mcc_disconnect_lost;
|
||||
Log.Info(message);
|
||||
break;
|
||||
|
||||
case ChatBot.DisconnectReason.InGameKick:
|
||||
Log.Info(Translations.mcc_disconnect_server);
|
||||
Log.Info(message);
|
||||
break;
|
||||
|
||||
case ChatBot.DisconnectReason.LoginRejected:
|
||||
Log.Info(Translations.mcc_disconnect_login);
|
||||
Log.Info(message);
|
||||
break;
|
||||
|
||||
case ChatBot.DisconnectReason.UserLogout:
|
||||
throw new InvalidOperationException(Translations.exception_user_logout);
|
||||
}
|
||||
|
||||
//Process AutoRelog last to make sure other bots can perform their cleanup tasks first (issue #1517)
|
||||
List<ChatBot> onDisconnectBotList = bots.Where(bot => bot is not AutoRelog).ToList();
|
||||
onDisconnectBotList.AddRange(bots.Where(bot => bot is AutoRelog));
|
||||
|
||||
foreach (ChatBot bot in onDisconnectBotList)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Warn("OnDisconnect: Got error from " + bot.ToString() + ": " + e.ToString());
|
||||
will_restart |= bot.OnDisconnect(reason, message);
|
||||
}
|
||||
else throw; //ThreadAbortException should not be caught
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is not ThreadAbortException)
|
||||
{
|
||||
Log.Warn("OnDisconnect: Got error from " + bot.ToString() + ": " + e.ToString());
|
||||
}
|
||||
else throw; //ThreadAbortException should not be caught
|
||||
}
|
||||
}
|
||||
|
||||
SentrySdk.EndSession();
|
||||
|
||||
if (!will_restart)
|
||||
{
|
||||
StopConsoleSession();
|
||||
Program.HandleFailure(null, false, reason);
|
||||
}
|
||||
}
|
||||
|
||||
SentrySdk.EndSession();
|
||||
|
||||
if (!will_restart)
|
||||
finally
|
||||
{
|
||||
StopConsoleSession();
|
||||
Program.HandleFailure(null, false, reason);
|
||||
Interlocked.Exchange(ref disconnectInProgress, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,13 @@ namespace MinecraftClient
|
|||
private static bool useMcVersionOnce = false;
|
||||
private static Thread? _restartThread = null;
|
||||
private static readonly object _restartLock = new();
|
||||
private static int _exitInProgress = 0;
|
||||
private static int _restartInProgress = 0;
|
||||
private static int _failureInProgress = 0;
|
||||
private static int _restartAttemptGeneration = 0;
|
||||
private static int _restartPending = 0;
|
||||
private static int _pendingRestartDelaySeconds = 0;
|
||||
private static bool _pendingRestartKeepAccountAndServerSettings = false;
|
||||
private static string settingsIniPath = "MinecraftClient.ini";
|
||||
|
||||
// [SENTRY]
|
||||
|
|
@ -914,22 +921,91 @@ namespace MinecraftClient
|
|||
|
||||
internal static bool TryRestart(int delaySeconds = 0, bool keepAccountAndServerSettings = false)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _restartInProgress, 1) != 0)
|
||||
{
|
||||
lock (_restartLock)
|
||||
{
|
||||
Interlocked.Exchange(ref _restartPending, 1);
|
||||
_pendingRestartDelaySeconds = delaySeconds;
|
||||
_pendingRestartKeepAccountAndServerSettings = keepAccountAndServerSettings;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int attemptGeneration = Interlocked.Increment(ref _restartAttemptGeneration);
|
||||
|
||||
lock (_restartLock)
|
||||
{
|
||||
if (HasRestartPendingForAnotherThreadNoLock())
|
||||
{
|
||||
Interlocked.Exchange(ref _restartInProgress, 0);
|
||||
Interlocked.Decrement(ref _restartAttemptGeneration);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (ConsoleIO.Backend is not null)
|
||||
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
ConsoleIO.Backend?.StopReadThread();
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
ConsoleIO.CancelAutocomplete();
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
ConsoleIO.Reset();
|
||||
}
|
||||
catch { }
|
||||
|
||||
ConsoleIO.Backend?.StopReadThread();
|
||||
var thread = new Thread(new ThreadStart(delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
if (client is not null) { client.Disconnect(); ConsoleIO.Reset(); }
|
||||
McClient? activeClient = client;
|
||||
if (activeClient is not null)
|
||||
{
|
||||
activeClient.Disconnect();
|
||||
if (ReferenceEquals(client, activeClient))
|
||||
client = null;
|
||||
ConsoleIO.Reset();
|
||||
}
|
||||
try
|
||||
{
|
||||
ConsoleIO.CancelAutocomplete();
|
||||
ConsoleIO.Reset();
|
||||
}
|
||||
catch { }
|
||||
if (offlinePrompt is not null)
|
||||
{
|
||||
if (ConsoleIO.Backend is not null)
|
||||
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
|
||||
offlinePrompt.Item2.Cancel(); offlinePrompt.Item1.Join(); offlinePrompt = null; ConsoleIO.Reset();
|
||||
try
|
||||
{
|
||||
offlinePrompt.Item2.Cancel();
|
||||
if (offlinePrompt.Item1.IsAlive)
|
||||
{
|
||||
if (!offlinePrompt.Item1.Join(TimeSpan.FromSeconds(1)))
|
||||
offlinePrompt.Item1.Interrupt();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Best-effort cleanup; the reconnect loop must continue.
|
||||
}
|
||||
finally
|
||||
{
|
||||
offlinePrompt = null;
|
||||
ConsoleIO.Reset();
|
||||
}
|
||||
}
|
||||
if (delaySeconds > 0)
|
||||
{
|
||||
|
|
@ -942,11 +1018,30 @@ namespace MinecraftClient
|
|||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _restartInProgress, 0);
|
||||
lock (_restartLock)
|
||||
{
|
||||
if (_restartThread == Thread.CurrentThread)
|
||||
_restartThread = null;
|
||||
}
|
||||
|
||||
if (Volatile.Read(ref _restartPending) != 0)
|
||||
{
|
||||
int nextDelaySeconds = 0;
|
||||
bool nextKeepAccountAndServerSettings = false;
|
||||
lock (_restartLock)
|
||||
{
|
||||
if (Volatile.Read(ref _restartPending) != 0)
|
||||
{
|
||||
nextDelaySeconds = _pendingRestartDelaySeconds;
|
||||
nextKeepAccountAndServerSettings = _pendingRestartKeepAccountAndServerSettings;
|
||||
Interlocked.Exchange(ref _restartPending, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (Volatile.Read(ref _restartPending) == 0)
|
||||
TryRestart(nextDelaySeconds, nextKeepAccountAndServerSettings);
|
||||
}
|
||||
}
|
||||
}));
|
||||
_restartThread = thread;
|
||||
|
|
@ -957,29 +1052,65 @@ namespace MinecraftClient
|
|||
|
||||
private static bool HasRestartPendingForAnotherThreadNoLock()
|
||||
{
|
||||
return _restartThread is not null
|
||||
&& _restartThread.IsAlive
|
||||
&& _restartThread != Thread.CurrentThread;
|
||||
return Volatile.Read(ref _restartPending) != 0
|
||||
|| (_restartThread is not null
|
||||
&& _restartThread.IsAlive
|
||||
&& _restartThread != Thread.CurrentThread);
|
||||
}
|
||||
|
||||
public static void DoExit(int exitcode = 0)
|
||||
{
|
||||
WriteBackSettings();
|
||||
ConsoleIO.WriteLineFormatted("§a" + string.Format(Translations.config_saving, settingsIniPath));
|
||||
if (Interlocked.Exchange(ref _exitInProgress, 1) != 0)
|
||||
return;
|
||||
|
||||
Interlocked.Exchange(ref _failureInProgress, 1);
|
||||
Interlocked.Exchange(ref _restartInProgress, 1);
|
||||
|
||||
try
|
||||
{
|
||||
WriteBackSettings();
|
||||
ConsoleIO.WriteLineFormatted("§a" + string.Format(Translations.config_saving, settingsIniPath));
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
McClient? activeClient = client;
|
||||
if (activeClient is not null)
|
||||
{
|
||||
activeClient.Disconnect();
|
||||
if (ReferenceEquals(client, activeClient))
|
||||
client = null;
|
||||
ConsoleIO.Reset();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (client is not null) { client.Disconnect(); ConsoleIO.Reset(); }
|
||||
if (offlinePrompt is not null)
|
||||
{
|
||||
if (ConsoleIO.Backend is not null)
|
||||
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
|
||||
offlinePrompt.Item2.Cancel();
|
||||
if (Thread.CurrentThread != offlinePrompt.Item1)
|
||||
offlinePrompt.Item1.Join(1000);
|
||||
offlinePrompt = null;
|
||||
ConsoleIO.Reset();
|
||||
try
|
||||
{
|
||||
if (ConsoleIO.Backend is not null)
|
||||
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
|
||||
offlinePrompt.Item2.Cancel();
|
||||
if (offlinePrompt.Item1.IsAlive)
|
||||
{
|
||||
if (!offlinePrompt.Item1.Join(TimeSpan.FromSeconds(1)))
|
||||
offlinePrompt.Item1.Interrupt();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
offlinePrompt = null;
|
||||
ConsoleIO.Reset();
|
||||
}
|
||||
}
|
||||
if (Config.Main.Advanced.PlayerHeadAsIcon && OperatingSystem.IsWindows()) { ConsoleIcon.RevertToMCCIcon(); }
|
||||
ConsoleIO.Backend?.Shutdown();
|
||||
try { ConsoleIO.Backend?.Shutdown(); } catch { }
|
||||
Interlocked.Exchange(ref _failureInProgress, 0);
|
||||
Interlocked.Exchange(ref _restartInProgress, 0);
|
||||
Interlocked.Exchange(ref _exitInProgress, 0);
|
||||
Environment.Exit(exitcode);
|
||||
}
|
||||
|
||||
|
|
@ -988,7 +1119,7 @@ namespace MinecraftClient
|
|||
/// </summary>
|
||||
public static void Exit(int exitcode = 0)
|
||||
{
|
||||
new Thread(() => { DoExit(exitcode); }).Start();
|
||||
DoExit(exitcode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1000,143 +1131,158 @@ namespace MinecraftClient
|
|||
/// <param name="disconnectReason">If set, the error message will be processed by the AutoRelog bot</param>
|
||||
public static void HandleFailure(string? errorMessage = null, bool versionError = false, ChatBot.DisconnectReason? disconnectReason = null)
|
||||
{
|
||||
bool autoRelogHandled = false;
|
||||
if (Volatile.Read(ref _restartInProgress) != 0 || Volatile.Read(ref _restartPending) != 0)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
if (Interlocked.Exchange(ref _failureInProgress, 1) != 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
ConsoleIO.Reset();
|
||||
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
|
||||
bool autoRelogHandled = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
try
|
||||
ConsoleIO.Reset();
|
||||
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
|
||||
{
|
||||
while (Console.KeyAvailable)
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
ConsoleIO.WriteLine(errorMessage);
|
||||
|
||||
if (disconnectReason.HasValue)
|
||||
{
|
||||
autoRelogHandled = true;
|
||||
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (InternalConfig.InteractiveMode)
|
||||
{
|
||||
if (versionError)
|
||||
{
|
||||
ConsoleIO.WriteLine(Translations.mcc_server_version);
|
||||
InternalConfig.MinecraftVersion = ConsoleIO.ReadLine();
|
||||
if (InternalConfig.MinecraftVersion != "")
|
||||
{
|
||||
useMcVersionOnce = true;
|
||||
Restart(0, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!autoRelogHandled && disconnectReason.HasValue)
|
||||
{
|
||||
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage!))
|
||||
return;
|
||||
}
|
||||
|
||||
if (offlinePrompt is null)
|
||||
{
|
||||
ConsoleIO.Backend?.StopReadThread();
|
||||
if (ConsoleIO.Backend is not null)
|
||||
ConsoleIO.Backend.OnInputChange += ConsoleIO.OfflineAutocompleteHandler;
|
||||
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
offlinePrompt = new(new Thread(new ThreadStart(delegate
|
||||
{
|
||||
bool exitThread = false;
|
||||
string command = " ";
|
||||
ConsoleIO.WriteLine(string.Empty);
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_disconnected, 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);
|
||||
|
||||
while (!cancellationTokenSource.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
if (exitThread)
|
||||
return;
|
||||
|
||||
command = ConsoleIO.ReadLine().Trim();
|
||||
|
||||
if (command.Length == 0)
|
||||
{
|
||||
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
|
||||
Commands.Exit.DoExit(Config.AppVar.ExpandVars(command));
|
||||
continue;
|
||||
}
|
||||
|
||||
string message = "";
|
||||
|
||||
if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
|
||||
&& command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
|
||||
command = command[1..];
|
||||
|
||||
if (command.StartsWith("reco"))
|
||||
{
|
||||
message = Commands.Reco.DoReconnect(Config.AppVar.ExpandVars(command));
|
||||
if (message == "")
|
||||
{
|
||||
exitThread = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (command.StartsWith("connect"))
|
||||
{
|
||||
message = Commands.Connect.DoConnect(Config.AppVar.ExpandVars(command));
|
||||
if (message == "")
|
||||
{
|
||||
exitThread = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (command.StartsWith("exit") || command.StartsWith("quit"))
|
||||
{
|
||||
message = Commands.Exit.DoExit(Config.AppVar.ExpandVars(command));
|
||||
}
|
||||
else if (command.StartsWith("help"))
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§8MCC: " +
|
||||
Config.Main.Advanced.InternalCmdChar.ToLogString() +
|
||||
new Commands.Reco().GetCmdDescTranslated());
|
||||
ConsoleIO.WriteLineFormatted("§8MCC: " +
|
||||
Config.Main.Advanced.InternalCmdChar.ToLogString() +
|
||||
new Commands.Connect().GetCmdDescTranslated());
|
||||
}
|
||||
else
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.icmd_unknown, command.Split(' ')[0]));
|
||||
|
||||
if (message != "")
|
||||
ConsoleIO.WriteLineFormatted("§8MCC: " + message);
|
||||
while (Console.KeyAvailable)
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
})), cancellationTokenSource);
|
||||
offlinePrompt.Item1.Start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not in interactive mode, just exit and let the calling script handle the failure
|
||||
if (disconnectReason.HasValue)
|
||||
{
|
||||
// Return distinct exit codes for known failures.
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.UserLogout) Exit(1);
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.InGameKick) Exit(2);
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.ConnectionLost) Exit(3);
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.LoginRejected) Exit(4);
|
||||
}
|
||||
Exit();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
ConsoleIO.WriteLine(errorMessage);
|
||||
|
||||
if (disconnectReason.HasValue)
|
||||
{
|
||||
autoRelogHandled = true;
|
||||
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (InternalConfig.InteractiveMode)
|
||||
{
|
||||
if (versionError)
|
||||
{
|
||||
ConsoleIO.WriteLine(Translations.mcc_server_version);
|
||||
InternalConfig.MinecraftVersion = ConsoleIO.ReadLine();
|
||||
if (InternalConfig.MinecraftVersion != "")
|
||||
{
|
||||
useMcVersionOnce = true;
|
||||
Restart(0, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!autoRelogHandled && disconnectReason.HasValue)
|
||||
{
|
||||
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage!))
|
||||
return;
|
||||
}
|
||||
|
||||
if (offlinePrompt is null)
|
||||
{
|
||||
ConsoleIO.Backend?.StopReadThread();
|
||||
if (ConsoleIO.Backend is not null)
|
||||
ConsoleIO.Backend.OnInputChange += ConsoleIO.OfflineAutocompleteHandler;
|
||||
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
offlinePrompt = new(new Thread(new ThreadStart(delegate
|
||||
{
|
||||
bool exitThread = false;
|
||||
string command = " ";
|
||||
ConsoleIO.WriteLine(string.Empty);
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_disconnected, 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);
|
||||
|
||||
while (!cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
if (exitThread)
|
||||
return;
|
||||
|
||||
command = ConsoleIO.ReadLineWithTimeout(TimeSpan.FromSeconds(1)).Trim();
|
||||
|
||||
if (command.Length == 0)
|
||||
{
|
||||
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
|
||||
{
|
||||
try { Commands.Exit.DoExit(Config.AppVar.ExpandVars(command)); }
|
||||
catch { }
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
string message = "";
|
||||
|
||||
if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
|
||||
&& command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
|
||||
command = command[1..];
|
||||
|
||||
if (command.StartsWith("reco"))
|
||||
{
|
||||
message = Commands.Reco.DoReconnect(Config.AppVar.ExpandVars(command));
|
||||
if (message == "")
|
||||
{
|
||||
exitThread = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (command.StartsWith("connect"))
|
||||
{
|
||||
message = Commands.Connect.DoConnect(Config.AppVar.ExpandVars(command));
|
||||
if (message == "")
|
||||
{
|
||||
exitThread = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (command.StartsWith("exit") || command.StartsWith("quit"))
|
||||
{
|
||||
message = Commands.Exit.DoExit(Config.AppVar.ExpandVars(command));
|
||||
}
|
||||
else if (command.StartsWith("help"))
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§8MCC: " +
|
||||
Config.Main.Advanced.InternalCmdChar.ToLogString() +
|
||||
new Commands.Reco().GetCmdDescTranslated());
|
||||
ConsoleIO.WriteLineFormatted("§8MCC: " +
|
||||
Config.Main.Advanced.InternalCmdChar.ToLogString() +
|
||||
new Commands.Connect().GetCmdDescTranslated());
|
||||
}
|
||||
else
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.icmd_unknown, command.Split(' ')[0]));
|
||||
|
||||
if (message != "")
|
||||
ConsoleIO.WriteLineFormatted("§8MCC: " + message);
|
||||
}
|
||||
})), cancellationTokenSource);
|
||||
offlinePrompt.Item1.Start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not in interactive mode, just exit and let the calling script handle the failure
|
||||
if (disconnectReason.HasValue)
|
||||
{
|
||||
// Return distinct exit codes for known failures.
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.UserLogout) Exit(1);
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.InGameKick) Exit(2);
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.ConnectionLost) Exit(3);
|
||||
if (disconnectReason.Value == ChatBot.DisconnectReason.LoginRejected) Exit(4);
|
||||
}
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _failureInProgress, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -4085,17 +4085,38 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (netMain is not null)
|
||||
{
|
||||
netMain.Item2.Cancel();
|
||||
netMain = null;
|
||||
}
|
||||
|
||||
if (netReader is not null)
|
||||
{
|
||||
netReader.Item2.Cancel();
|
||||
socketWrapper.Disconnect();
|
||||
netReader = null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
socketWrapper.Disconnect();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort; the old protocol loop should not block shutdown.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
packetQueue.CompleteAdding();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort; the queue may already be completed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using MinecraftClient.Crypto;
|
||||
|
||||
|
|
@ -61,10 +62,31 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
int read = 0;
|
||||
while (read < offset)
|
||||
{
|
||||
if (encrypted)
|
||||
read += s!.Read(buffer, start + read, offset - read);
|
||||
else
|
||||
read += c.Client.Receive(buffer, start + read, offset - read, f);
|
||||
int received;
|
||||
try
|
||||
{
|
||||
if (encrypted)
|
||||
received = s!.Read(buffer, start + read, offset - read);
|
||||
else
|
||||
received = c.Client.Receive(buffer, start + read, offset - read, f);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (received <= 0)
|
||||
throw new IOException("Socket closed while reading from the server.");
|
||||
|
||||
read += received;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
|
@ -151,6 +152,15 @@ namespace MinecraftClient
|
|||
public string? LegacyBackupPath { get; init; }
|
||||
}
|
||||
|
||||
private static Mutex AcquireSettingsFileMutex(string filepath)
|
||||
{
|
||||
string fullPath = Path.GetFullPath(filepath);
|
||||
string normalizedPath = fullPath.Replace(Path.DirectorySeparatorChar, '/').ToLowerInvariant();
|
||||
byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(normalizedPath));
|
||||
string mutexName = "MCC.Settings." + Convert.ToHexString(hashBytes)[..16];
|
||||
return new Mutex(false, mutexName);
|
||||
}
|
||||
|
||||
public static ConfigLoadResult LoadFromFile(string filepath, bool keepAccountAndServerSettings = false)
|
||||
{
|
||||
bool keepAccountSettings = InternalConfig.KeepAccountSettings;
|
||||
|
|
@ -158,118 +168,142 @@ namespace MinecraftClient
|
|||
if (keepAccountAndServerSettings)
|
||||
InternalConfig.KeepAccountSettings = InternalConfig.KeepServerSettings = true;
|
||||
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
TomlDocument document;
|
||||
using var settingsMutex = AcquireSettingsFileMutex(filepath);
|
||||
settingsMutex.WaitOne();
|
||||
try
|
||||
{
|
||||
document = TomlParser.ParseFile(filepath);
|
||||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||
|
||||
Config = TomletMain.To<GlobalConfig>(document);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
TomlDocument document;
|
||||
try
|
||||
{
|
||||
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);
|
||||
return new ConfigLoadResult
|
||||
{
|
||||
Success = false,
|
||||
NeedWriteDefault = true,
|
||||
IsLegacyUpgrade = true,
|
||||
LegacyBackupPath = newFilePath
|
||||
};
|
||||
}
|
||||
document = TomlParser.ParseFile(filepath);
|
||||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||
|
||||
Config = TomletMain.To<GlobalConfig>(document);
|
||||
}
|
||||
catch { }
|
||||
return new ConfigLoadResult
|
||||
catch (Exception ex)
|
||||
{
|
||||
Success = false,
|
||||
NeedWriteDefault = false,
|
||||
ErrorMessage = ex.GetFullMessage()
|
||||
};
|
||||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||
try
|
||||
{
|
||||
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);
|
||||
return new ConfigLoadResult
|
||||
{
|
||||
Success = false,
|
||||
NeedWriteDefault = true,
|
||||
IsLegacyUpgrade = true,
|
||||
LegacyBackupPath = newFilePath
|
||||
};
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return new ConfigLoadResult
|
||||
{
|
||||
Success = false,
|
||||
NeedWriteDefault = false,
|
||||
ErrorMessage = ex.GetFullMessage()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!keepAccountSettings)
|
||||
InternalConfig.KeepAccountSettings = false;
|
||||
if (!keepServerSettings)
|
||||
InternalConfig.KeepServerSettings = false;
|
||||
}
|
||||
return new ConfigLoadResult { Success = true, NeedWriteDefault = false };
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!keepAccountSettings)
|
||||
InternalConfig.KeepAccountSettings = false;
|
||||
if (!keepServerSettings)
|
||||
InternalConfig.KeepServerSettings = false;
|
||||
settingsMutex.ReleaseMutex();
|
||||
}
|
||||
return new ConfigLoadResult { Success = true, NeedWriteDefault = false };
|
||||
}
|
||||
|
||||
public static void WriteToFile(string filepath, bool backupOldFile)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
string tomlString = TomletMain.TomlStringFrom(Config);
|
||||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||
|
||||
string[] tomlList = tomlString.Split('\n');
|
||||
StringBuilder newConfig = new();
|
||||
foreach (string line in tomlList)
|
||||
using var settingsMutex = AcquireSettingsFileMutex(filepath);
|
||||
settingsMutex.WaitOne();
|
||||
try
|
||||
{
|
||||
Match matchComment = CommentRegex.Match(line);
|
||||
if (matchComment.Success && matchComment.Groups.Count == 3)
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
string tomlString = TomletMain.TomlStringFrom(Config);
|
||||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||
|
||||
string[] tomlList = tomlString.Split('\n');
|
||||
StringBuilder newConfig = new();
|
||||
foreach (string line in tomlList)
|
||||
{
|
||||
string config = matchComment.Groups[1].Value, comment = matchComment.Groups[2].Value;
|
||||
if (config.Length > 0)
|
||||
newConfig.Append(config).Append(' ', Math.Max(1, CommentsAlignPosition - config.Length) - 1);
|
||||
string? comment_trans = ConfigComments.ResourceManager.GetString(comment);
|
||||
if (string.IsNullOrEmpty(comment_trans))
|
||||
newConfig.Append("# ").AppendLine(comment.ReplaceLineEndings());
|
||||
Match matchComment = CommentRegex.Match(line);
|
||||
if (matchComment.Success && matchComment.Groups.Count == 3)
|
||||
{
|
||||
string config = matchComment.Groups[1].Value, comment = matchComment.Groups[2].Value;
|
||||
if (config.Length > 0)
|
||||
newConfig.Append(config).Append(' ', Math.Max(1, CommentsAlignPosition - config.Length) - 1);
|
||||
string? comment_trans = ConfigComments.ResourceManager.GetString(comment);
|
||||
if (string.IsNullOrEmpty(comment_trans))
|
||||
newConfig.Append("# ").AppendLine(comment.ReplaceLineEndings());
|
||||
else
|
||||
newConfig.Append("# ").AppendLine(comment_trans.Replace("\n", "\n# ").ReplaceLineEndings());
|
||||
}
|
||||
else
|
||||
newConfig.Append("# ").AppendLine(comment_trans.Replace("\n", "\n# ").ReplaceLineEndings());
|
||||
}
|
||||
else
|
||||
{
|
||||
newConfig.AppendLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
bool needUpdate = true;
|
||||
byte[] newConfigByte = Encoding.UTF8.GetBytes(newConfig.ToString());
|
||||
if (File.Exists(filepath))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (new FileInfo(filepath).Length == newConfigByte.Length)
|
||||
if (File.ReadAllBytes(filepath).SequenceEqual(newConfigByte))
|
||||
needUpdate = false;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
bool backupSuccessed = true;
|
||||
if (backupOldFile && File.Exists(filepath))
|
||||
{
|
||||
string backupFilePath = Path.ChangeExtension(filepath, ".backup.ini");
|
||||
try { File.Copy(filepath, backupFilePath, true); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
backupSuccessed = false;
|
||||
ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.config_backup_fail, backupFilePath));
|
||||
ConsoleIO.WriteLine(ex.Message);
|
||||
newConfig.AppendLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (backupSuccessed)
|
||||
bool needUpdate = true;
|
||||
byte[] newConfigByte = Encoding.UTF8.GetBytes(newConfig.ToString());
|
||||
if (File.Exists(filepath))
|
||||
{
|
||||
try { File.WriteAllBytes(filepath, newConfigByte); }
|
||||
catch (Exception ex)
|
||||
try
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.config_write_fail, filepath));
|
||||
ConsoleIO.WriteLine(ex.Message);
|
||||
if (new FileInfo(filepath).Length == newConfigByte.Length)
|
||||
if (File.ReadAllBytes(filepath).SequenceEqual(newConfigByte))
|
||||
needUpdate = false;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
bool backupSuccessed = true;
|
||||
if (backupOldFile && File.Exists(filepath))
|
||||
{
|
||||
string backupFilePath = Path.ChangeExtension(filepath, ".backup.ini");
|
||||
try { File.Copy(filepath, backupFilePath, true); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
backupSuccessed = false;
|
||||
ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.config_backup_fail, backupFilePath));
|
||||
ConsoleIO.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
if (backupSuccessed)
|
||||
{
|
||||
string tempFilePath = filepath + ".tmp";
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(tempFilePath, newConfigByte);
|
||||
File.Move(tempFilePath, filepath, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { File.Delete(tempFilePath); } catch { }
|
||||
ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.config_write_fail, filepath));
|
||||
ConsoleIO.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
settingsMutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue