diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 913d48e5..e1449079 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -75,8 +75,8 @@ namespace MinecraftClient private int sequenceId; // User for player block synchronization (Aka. digging, placing blocks, etc..) private bool CanSendMessage = false; - private readonly string host; - private readonly int port; + private string host; + private int port; private readonly int protocolversion; private readonly string username; private Guid uuid; @@ -159,6 +159,7 @@ namespace MinecraftClient SessionToken _sessionToken; CancellationTokenSource? cmdprompt = null; Tuple? timeoutdetector = null; + private int transferInProgress = 0; public ILogger Log; @@ -335,16 +336,34 @@ namespace MinecraftClient public void Transfer(string newHost, int newPort) { + // Do not block here: a new handler can start processing packets before the + // previous transfer call fully unwinds, and waiting can deadlock main-thread work. + if (Interlocked.CompareExchange(ref transferInProgress, 1, 0) != 0) + { + Log.Warn($"Ignoring overlapping transfer to {newHost}:{newPort} because another transfer is still in progress."); + return; + } + + IMinecraftCom oldHandler = handler; + TcpClient oldClient = client; + try { - Log.Info($"Initiating a transfer to: {host}:{port}"); + Log.Info($"Initiating a transfer to: {newHost}:{newPort}"); // Unload bots UnloadAllBots(); bots.Clear(); + + ResetStateForTransfer(); - // Close existing connection - client.Close(); + // Retire the old handler so its updater exits without reporting a stale disconnect. + oldHandler.Dispose(); + oldClient.Close(); + + host = newHost; + port = newPort; + UpdateKeepAlive(); // Establish new connection client = ProxyHandler.NewTcpClient(newHost, newPort); @@ -353,20 +372,17 @@ namespace MinecraftClient // Reinitialize the protocol handler handler = Protocol.ProtocolHandler.GetProtocolHandler(client, protocolversion, null, this); - Log.Info($"Connected to {host}:{port}"); + Log.Info($"Connected to {newHost}:{newPort}"); // Retry login process - if (handler.Login(playerKeyPair, _sessionToken)) + if (handler.Login(playerKeyPair, _sessionToken, isTransfer: true)) { foreach (var bot in botsOnHold) BotLoad(bot, false); botsOnHold.Clear(); - Log.Info("Successfully transferred connection and logged in."); - cmdprompt = new CancellationTokenSource(); - ConsoleInteractive.ConsoleReader.BeginReadThread(); - ConsoleInteractive.ConsoleReader.MessageReceived += ConsoleReaderOnMessageReceived; - ConsoleInteractive.ConsoleReader.OnInputChange += ConsoleIO.AutocompleteHandler; + UpdateKeepAlive(); + Log.Info($"Successfully transferred connection and logged in to {newHost}:{newPort}."); } else { @@ -378,6 +394,22 @@ namespace MinecraftClient { Log.Error($"Transfer to {newHost}:{newPort} failed: {ex.Message}"); + try + { + handler.Dispose(); + } + catch + { + } + + try + { + client.Close(); + } + catch + { + } + // Handle reconnection attempts if (timeoutdetector is not null) { @@ -400,8 +432,35 @@ namespace MinecraftClient Program.HandleFailure(); } - throw new Exception("Transfer failed and reconnection attempts exhausted."); + throw new Exception("Transfer failed and reconnection attempts exhausted.", ex); } + finally + { + Interlocked.Exchange(ref transferInProgress, 0); + } + } + + private void ResetStateForTransfer() + { + ClearTasks(); + ConsoleIO.CancelAutocomplete(); + SetCanSendMessage(false); + + locationReceived = false; + physicsInitialized = false; + isUnderSlab = false; + path = null; + pathTarget = null; + _yaw = null; + _pitch = null; + LastDigPosition = null; + RemainingDiggingTime = 0; + nextSneakingUpdate = DateTime.Now; + + physicsInput.Reset(); + world.Clear(); + entities.Clear(); + ClearInventories(); } /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 8f8a8723..15d20b71 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -618,7 +618,7 @@ namespace MinecraftClient.Protocol.Handlers } } - public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session) + public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session, bool isTransfer = false) { if (Handshake(handler.GetUserUuidStr(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort(), session)) { diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index d203e90f..84c7baac 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -3188,8 +3188,13 @@ namespace MinecraftClient.Protocol.Handlers /// Do the Minecraft login. /// /// True if login successful - public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session) + public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session, bool isTransfer = false) { + int nextState = isTransfer && protocolVersion >= MC_1_20_6_Version ? 3 : 2; + + if (nextState == 3) + log.Debug("Using transfer handshake intent for transferred login."); + // 1. Send the handshake packet SendPacket(0x00, dataTypes.ConcatBytes( // Protocol Version (use raw version for snapshot/RC servers) @@ -3202,7 +3207,7 @@ namespace MinecraftClient.Protocol.Handlers dataTypes.GetUShort((ushort)handler.GetServerPort()), // Next State - DataTypes.GetVarInt(2)) // 2 is for the Login state + DataTypes.GetVarInt(nextState)) // 2 is Login, 3 is Transfer ); // 2. Send the Login Start packet diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index 928659b0..6c7dd596 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -19,7 +19,7 @@ namespace MinecraftClient.Protocol /// Start the login procedure once connected to the server /// /// True if login was successful - bool Login(PlayerKeyPair? playerKeyPair, Session.SessionToken session); + bool Login(PlayerKeyPair? playerKeyPair, Session.SessionToken session, bool isTransfer = false); /// /// Disconnect from the server