From c391d290eca2a4751a61f939bab0df5d45e4ef9a Mon Sep 17 00:00:00 2001 From: JuiceyDev Date: Thu, 5 Mar 2026 20:03:06 +0100 Subject: [PATCH] Networking Fixes & Updates --- Minecraft.Client/Network/ClientConnection.cpp | 14 +++++- .../Network/PendingConnection.cpp | 3 +- Minecraft.Client/Network/PendingConnection.h | 2 +- Minecraft.World/Network/Connection.cpp | 16 +++++-- .../Network/Packets/DisconnectPacket.cpp | 2 + Minecraft.World/Network/Packets/Packet.cpp | 43 ++++++++++++++----- 6 files changed, 64 insertions(+), 16 deletions(-) diff --git a/Minecraft.Client/Network/ClientConnection.cpp b/Minecraft.Client/Network/ClientConnection.cpp index 8d74ac115..425f3f5d6 100644 --- a/Minecraft.Client/Network/ClientConnection.cpp +++ b/Minecraft.Client/Network/ClientConnection.cpp @@ -1134,6 +1134,15 @@ void ClientConnection::handleTileUpdate(shared_ptr packet) void ClientConnection::handleDisconnect(shared_ptr packet) { +#ifdef __linux__ + // Linux fix: On local host connections, ignore DisconnectPacket. The singleplayer internal + // server should never disconnect itself. If we see this, it's likely stream desync reading + // garbage data as a DisconnectPacket. + if (connection && connection->getSocket() && connection->getSocket()->isLocal()) { + fprintf(stderr, "[CONN] Ignoring DisconnectPacket on local connection (reason=%d)\n", packet->reason); + return; + } +#endif connection->close(DisconnectPacket::eDisconnect_Kicked); done = true; @@ -1607,7 +1616,7 @@ void ClientConnection::handleEntityActionAtPosition(shared_ptr packet) { -// printf("Client: handlePreLogin\n"); + fprintf(stderr, "[LOGIN-CLI] handlePreLogin entered, isHost=%d, userIdx=%d\n", (int)g_NetworkManager.IsHost(), m_userIndex); #if 1 // 4J - Check that we can play with all the players already in the game who have Friends-Only UGC set BOOL canPlay = TRUE; @@ -2049,8 +2058,11 @@ void ClientConnection::handlePreLogin(shared_ptr packet) } BOOL allAllowed, friendsAllowed; ProfileManager.AllowedPlayerCreatedContent(m_userIndex,true,&allAllowed,&friendsAllowed); + fprintf(stderr, "[LOGIN] Sending LoginPacket: user=%ls netVer=%d userIdx=%d isHost=%d\n", + minecraft->user->name.c_str(), SharedConstants::NETWORK_PROTOCOL_VERSION, m_userIndex, (int)g_NetworkManager.IsHost()); send( shared_ptr( new LoginPacket(minecraft->user->name, SharedConstants::NETWORK_PROTOCOL_VERSION, offlineXUID, onlineXUID, (allAllowed!=TRUE && friendsAllowed==TRUE), packet->m_ugcPlayersVersion, app.GetPlayerSkinId(m_userIndex), app.GetPlayerCapeId(m_userIndex), ProfileManager.IsGuest( m_userIndex )))); + fprintf(stderr, "[LOGIN] LoginPacket sent successfully\n"); if(!g_NetworkManager.IsHost() ) { diff --git a/Minecraft.Client/Network/PendingConnection.cpp b/Minecraft.Client/Network/PendingConnection.cpp index 09b249634..8cd7b63a0 100644 --- a/Minecraft.Client/Network/PendingConnection.cpp +++ b/Minecraft.Client/Network/PendingConnection.cpp @@ -60,6 +60,7 @@ void PendingConnection::disconnect(DisconnectPacket::eDisconnectReason reason) { // try { // 4J - removed try/catch // logger.info("Disconnecting " + getName() + ": " + reason); + fprintf(stderr, "[PENDING] disconnect called with reason=%d at tick=%d\n", reason, _tick); app.DebugPrintf("Pending connection disconnect: %d\n", reason ); connection->send( shared_ptr( new DisconnectPacket(reason) ) ); connection->sendAndQuit(); @@ -139,7 +140,7 @@ void PendingConnection::sendPreLoginResponse() void PendingConnection::handleLogin(shared_ptr packet) { -// printf("Server: handleLogin\n"); + fprintf(stderr, "[LOGIN-SRV] handleLogin called! clientVersion=%d\n", packet->clientVersion); //name = packet->userName; if (packet->clientVersion != SharedConstants::NETWORK_PROTOCOL_VERSION) { diff --git a/Minecraft.Client/Network/PendingConnection.h b/Minecraft.Client/Network/PendingConnection.h index 506520092..380c7cf39 100644 --- a/Minecraft.Client/Network/PendingConnection.h +++ b/Minecraft.Client/Network/PendingConnection.h @@ -11,7 +11,7 @@ class PendingConnection : public PacketListener { private: static const int FAKE_LAG = 0; - static const int MAX_TICKS_BEFORE_LOGIN = 20 * 30; + static const int MAX_TICKS_BEFORE_LOGIN = 20 * 30 * 10; // 10 minutes instead of 20 sec for Linux theres just no login yet // public static Logger logger = Logger.getLogger("Minecraft"); static Random *random; diff --git a/Minecraft.World/Network/Connection.cpp b/Minecraft.World/Network/Connection.cpp index 757d53257..42c36b8a0 100644 --- a/Minecraft.World/Network/Connection.cpp +++ b/Minecraft.World/Network/Connection.cpp @@ -196,7 +196,9 @@ bool Connection::writeTick() LeaveCriticalSection(&writeLock); Packet::writePacket(packet, bufferedDos); - +#ifdef __linux__ + bufferedDos->flush(); // Ensure buffered data reaches socket before any other writes +#endif #ifndef _CONTENT_PACKAGE // 4J Added for debugging @@ -230,6 +232,13 @@ bool Connection::writeTick() // If the shouldDelay flag is still set at this point then we want to write it to QNet as a single packet with priority flags // Otherwise just buffer the packet with other outgoing packets as the java game did +#ifdef __linux__ + // Linux fix: For local connections, always use bufferedDos to avoid byte interleaving between + // the BufferedOutputStream buffer and direct sos writes. The shouldDelay/writeWithFlags path + // writes directly to sos, which can inject bytes BEFORE unflushed bufferedDos data. + Packet::writePacket(packet, bufferedDos); + bufferedDos->flush(); // Ensure data reaches socket immediately for delayed packets +#else if(packet->shouldDelay) { Packet::writePacket(packet, byteArrayDos); @@ -245,6 +254,7 @@ bool Connection::writeTick() { Packet::writePacket(packet, bufferedDos); } +#endif #ifndef _CONTENT_PACKAGE // 4J Added for debugging @@ -329,9 +339,9 @@ close("disconnect.genericReason", "Internal exception: " + e.toString()); void Connection::close(DisconnectPacket::eDisconnectReason reason, ...) { -// printf("Con:0x%x close\n",this); + fprintf(stderr, "[CONN] close called with reason=%d on connection=%p\n", reason, (void*)this); if (!running) return; -// printf("Con:0x%x close doing something\n",this); + fprintf(stderr, "[CONN] close proceeding (was running) on connection=%p\n", (void*)this); disconnected = true; va_list input; diff --git a/Minecraft.World/Network/Packets/DisconnectPacket.cpp b/Minecraft.World/Network/Packets/DisconnectPacket.cpp index bf696fd10..79d3f1005 100644 --- a/Minecraft.World/Network/Packets/DisconnectPacket.cpp +++ b/Minecraft.World/Network/Packets/DisconnectPacket.cpp @@ -20,10 +20,12 @@ DisconnectPacket::DisconnectPacket(eDisconnectReason reason) void DisconnectPacket::read(DataInputStream *dis) //throws IOException { reason = (eDisconnectReason)dis->readInt(); + fprintf(stderr, "[PKT] DisconnectPacket::read reason=%d\n", reason); } void DisconnectPacket::write(DataOutputStream *dos) //throws IOException { + fprintf(stderr, "[PKT] DisconnectPacket::write reason=%d\n", reason); dos->writeInt((int)reason); } diff --git a/Minecraft.World/Network/Packets/Packet.cpp b/Minecraft.World/Network/Packets/Packet.cpp index 2dfc84e17..e37103757 100644 --- a/Minecraft.World/Network/Packets/Packet.cpp +++ b/Minecraft.World/Network/Packets/Packet.cpp @@ -347,6 +347,14 @@ unordered_map Packet::statistics = unordered_ma shared_ptr Packet::readPacket(DataInputStream *dis, bool isServer) // throws IOException TODO 4J JEV, should this declare a throws? { + // N packet ID + static int s_clientPktHistory[64]; + static int s_clientPktIdx = 0; + static int s_serverPktHistory[64]; + static int s_serverPktIdx = 0; + static bool s_clientDesyncLogged = false; + static bool s_serverDesyncLogged = false; + int id = 0; shared_ptr packet = nullptr; @@ -358,16 +366,33 @@ shared_ptr Packet::readPacket(DataInputStream *dis, bool isServer) // th if ((isServer && serverReceivedPackets.find(id) == serverReceivedPackets.end()) || (!isServer && clientReceivedPackets.find(id) == clientReceivedPackets.end())) { - //app.DebugPrintf("Bad packet id %d\n", id); + int *history = isServer ? s_serverPktHistory : s_clientPktHistory; + int idx = isServer ? s_serverPktIdx : s_clientPktIdx; + bool &logged = isServer ? s_serverDesyncLogged : s_clientDesyncLogged; + + fprintf(stderr, "[PKT] Bad packet id %d (0x%x) isServer=%d\n", id, id, isServer); + if (!logged) { + logged = true; + fprintf(stderr, "[PKT] === PACKET HISTORY (last %d, newest last) ===\n", 64); + for (int i = 0; i < 64; i++) { + int h = history[(idx + i) % 64]; + if (h != 0) fprintf(stderr, "[PKT] pkt %d (0x%x)\n", h, h); + } + fprintf(stderr, "[PKT] === END HISTORY ===\n"); + } __debugbreak(); - assert(false); + //assert(false); + return nullptr; // throw new IOException(wstring(L"Bad packet id ") + _toString(id)); } + // Record successfully read packet ID + if (isServer) { s_serverPktHistory[s_serverPktIdx % 64] = id; s_serverPktIdx++; } + else { s_clientPktHistory[s_clientPktIdx % 64] = id; s_clientPktIdx++; } + packet = getPacket(id); - if (packet == NULL) assert(false);//throw new IOException(wstring(L"Bad packet id ") + _toString(id)); + if (packet == NULL) { fprintf(stderr, "[PKT] getPacket(%d) returned NULL\n", id); return nullptr; } - //app.DebugPrintf("%s reading packet %d\n", isServer ? "Server" : "Client", packet->getId()); packet->read(dis); // } // catch (EOFException e) @@ -425,15 +450,13 @@ wstring Packet::readUtf(DataInputStream *dis, int maxLength) // throws IOExcepti short stringLength = dis->readShort(); if (stringLength > maxLength) { - wstringstream stream; - stream << L"Received string length longer than maximum allowed (" << stringLength << " > " << maxLength << ")"; - assert(false); - // throw new IOException( stream.str() ); + fprintf(stderr, "[PKT] readUtf: string length %d > max %d (stream desync?)\n", stringLength, maxLength); + return L""; } if (stringLength < 0) { - assert(false); - // throw new IOException(L"Received string length is less than zero! Weird string!"); + fprintf(stderr, "[PKT] readUtf: negative string length %d (stream desync?)\n", stringLength); + return L""; } wstring builder = L"";