From a94f5f8c588081500cdbae71b02a84da5ca47fc7 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 1 Apr 2026 20:17:24 -0400 Subject: [PATCH] In game connection manager --- README.md | 29 ++- constants.js | 14 +- src/auth.js | 96 ++++----- src/javaclienthandler.js | 112 +++++----- src/lanbroadcast.js | 6 +- src/lceproxy.js | 434 ++++++++++++++++++++++++++++++--------- 6 files changed, 467 insertions(+), 224 deletions(-) diff --git a/README.md b/README.md index 063ab28..7c40542 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,38 @@ **Client modifications, or server plugins ARE NOT required!** - Install [NodeJS](https://nodejs.org/en/download), you may need to restart your computer after -- Start a localhost Java Edition server running on port 25564, and in offline mode (you can edit these in `server.properties`) - Run the file `install_dependencies.bat`, - you only need to run this file one time. -- Run `start.bat`, the Java Edition server will appear in the join tab on Legacy Console Edition! You can configure the server IP / PORT in `constants.js`. +- Run `start.bat`, the proxy will appear in the join tab on Legacy Console Edition! -# Known issues, will be fixed +--- -- Crafting is buggy -- You're unable to accept villager trades +# How to Use + +Use the `/lce` commands to manage your connection between Legacy Edition and Java Edition: + +- `/lce connect ` + Connect to a Java Edition server. + +- `/lce disconnect` + Disconnect from the Java Edition server. + +- `/lce link` + Link your Java Edition account. + +- `/lce unlink` + Unlink your Java Edition account. + +- `/lce uselink ` + Enable or disable the use of your linked Java Edition account when connecting. --- # TODO +- Crafting is buggy / not fully implemented +- You're unable to accept villager trades +- Lighting is a little bugged in other dimensions (afaik) + ## Chunks - [x] Java → LCE chunks diff --git a/constants.js b/constants.js index 1006b09..2e08188 100644 --- a/constants.js +++ b/constants.js @@ -1,14 +1,10 @@ /* --------------------------------------------------------------- */ /* constants.js */ /* --------------------------------------------------------------- */ -const USE_LEGACY_USERNAME = false; -const CUSTOM_USERNAME = "LCE_Player"; +const LOBBY_NAME = "Legacy Cross Play"; + +/* EVERYTHING IS NOW HANDLED IN GAME, JOIN THE PROXY DIRECTLY INSTEAD OF CONFIGURING THIS FILE! */ -const SERVERS = [ - { server: "us-1.creepernation.net:25568", cracked: true }, - { server: "hypixel.net", cracked: false }, - { server: "localhost:25564", cracked: true }, -]; /* DONT TOUCH THESE BELOW THINGS UNLESS YOU KNOW WHAT YOURE DOING! */ @@ -20,8 +16,6 @@ module.exports = { GAME_PORT, WIN64_LAN_DISCOVERY_PORT, MINECRAFT_NET_VERSION, - USE_LEGACY_USERNAME, - CUSTOM_USERNAME, - SERVERS, + LOBBY_NAME, }; /* --------------------------------------------------------------- */ diff --git a/src/auth.js b/src/auth.js index e926833..a36dc2f 100644 --- a/src/auth.js +++ b/src/auth.js @@ -6,6 +6,7 @@ const fs = require("fs"); const path = require("path"); const AUTH_CACHE_DIR = path.join(__dirname, "..", ".auth_cache"); +const USER_IDENTIFIER = "lce-proxy-user"; class MicrosoftAuth { constructor() { @@ -22,72 +23,65 @@ class MicrosoftAuth { } } - async authenticate() { + async authenticate(onDeviceCode) { this.ensureCacheDir(); - if (!this.authflow) { - const userIdentifier = "lce-proxy-user"; - this.authflow = new Authflow(userIdentifier, AUTH_CACHE_DIR); + this.authflow = new Authflow(USER_IDENTIFIER, AUTH_CACHE_DIR); + + if (onDeviceCode) { + this.authflow.codeCallback = onDeviceCode; } - if (!this.authflow.onMsaCode) { - this.authflow.onMsaCode = (data) => { - console.log("\nYou have not yet logged into your Java account!"); - const url = `https://microsoft.com/link?otc=${data.userCode}`; - console.log(`enter the code ${data.userCode} at ${url}`); - console.log( - "If you enabled this by accident and would like to only join offline servers, set USE_JAVA_ACCOUNT to false in constants.js\n", - ); + const result = await this.authflow.getMinecraftJavaToken({ + fetchProfile: true, + }); - try { - const { exec } = require("child_process"); - const platform = process.platform; - const command = - platform === "win32" - ? `start ${url}` - : platform === "darwin" - ? `open ${url}` - : `xdg-open ${url}`; - exec(command); - } catch (err) { - /* do nothing */ - } - }; + if (!result.profile) { + throw new Error( + "Couldn't log into Java Edition account! Do you own Minecraft Java Edition?", + ); } - try { - const result = await this.authflow.getMinecraftJavaToken({ - fetchProfile: true, - }); + this.username = result.profile.name; + this.uuid = result.profile.id; + this.accessToken = result.token; + this.authenticated = true; - if (!result.profile) { - throw new Error( - "Error logging into Java Edition account! Do you own Minecraft Java Edition?\n", - ); - } - - this.username = result.profile.name; - this.uuid = result.profile.id; - this.accessToken = result.token; - this.authenticated = true; - - return { - username: this.username, - uuid: this.uuid, - accessToken: this.accessToken, - }; - } catch (err) { - throw err; - } + return { + username: this.username, + uuid: this.uuid, + accessToken: this.accessToken, + }; } - async getAuthData() { - return await this.authenticate(); + unlink() { + try { + const files = fs.readdirSync(AUTH_CACHE_DIR); + for (const file of files) { + fs.unlinkSync(path.join(AUTH_CACHE_DIR, file)); + } + } catch (e) { + /* do nothing */ + } + this.authflow = null; + this.authenticated = false; + this.username = null; + this.uuid = null; + this.accessToken = null; } isAuthenticated() { return this.authenticated; } + + hasCachedAccount() { + try { + const files = fs.readdirSync(AUTH_CACHE_DIR); + return files.length > 0; + } catch (e) { + return false; + } + } } const authInstance = new MicrosoftAuth(); diff --git a/src/javaclienthandler.js b/src/javaclienthandler.js index b85e079..bd52974 100644 --- a/src/javaclienthandler.js +++ b/src/javaclienthandler.js @@ -5,7 +5,7 @@ //beware that there are a lot of magic numbers, need to do a refractor soon. const mc = require("minecraft-protocol"); -const { USE_LEGACY_USERNAME, CUSTOM_USERNAME } = require("../constants"); + const msAuth = require("./auth"); const PacketWriter = require("./packetwriter"); const { mapJavaItemToLCE, mapJavaBlockToLCE } = require("./mappings"); @@ -26,9 +26,7 @@ async function connectToJavaServer(proxy, client) { let authConfig = { host: serverConfig.host, port: serverConfig.port, - username: !USE_LEGACY_USERNAME - ? CUSTOM_USERNAME - : client.username || "undefined", + username: client.username || "Player", auth: "offline", version: "1.8.9", }; @@ -50,9 +48,6 @@ async function connectToJavaServer(proxy, client) { version: "1.8.9", }; } else { - console.log( - `Server ${serverConfig.name} requires authentication. Authenticating...`, - ); await msAuth.authenticate(); authConfig = { host: serverConfig.host, @@ -82,72 +77,61 @@ async function connectToJavaServer(proxy, client) { client.javaClient = javaClient; client._lastJavaPackets = []; + const returnToLobby = (message) => { + if (client.socket && client.socket.destroyed) return; + const old = client.javaClient; + client.javaClient = null; + client.inLobby = true; + if (old) { + old.removeAllListeners(); + old.on("error", () => { + /* do nothing */ + }); + try { + old.end(); + } catch (e) { + /* do nothing */ + } + } + proxy.enterLobby(client, message); + }; + javaClient.on("error", (err) => { - console.log(`DISCONNECTED: ${err}`); - if (!client._removing) { - client._removing = true; - const index = proxy.clients.indexOf(client); - if (index > -1) { - proxy.clients.splice(index, 1); - } - if (client.smallId) { - proxy.usedSmallIds.delete(client.smallId); - } - client.javaClient = null; - if (client.socket && !client.socket.destroyed) { - try { - const writer = new PacketWriter(); - writer.writeInt(20); - proxy.sendPacket(client, 0xff, writer.toBuffer()); - client.socket.destroy(); - } catch (err) { - /* do nothing */ - } - } - } + returnToLobby(`Connection error: ${err.message || err}`); }); - javaClient.on("disconnect", (packet) => { - console.log(`DISCONNECTED: ${packet.reason}`); - if (!client._removing) { - proxy.disconnectClient(client, 2); + const handleDisconnectReason = (raw, prefix) => { + let text = ""; + try { + text = typeof raw === "string" ? raw : JSON.parse(raw)?.text || raw; + } catch (e) { + text = String(raw); } - }); - - javaClient.on("kick_disconnect", (packet) => { - console.log(`DISCONNECTED: ${packet.reason}`); - if (client.state === "play") { - let reason = 10; - if (packet.reason) { - const reasonText = - typeof packet.reason === "string" - ? packet.reason - : JSON.stringify(packet.reason); - const lowerReason = reasonText.toLowerCase(); - if (lowerReason.includes("ban")) { - reason = 10; - } else if (lowerReason.includes("kick")) { - reason = 10; - } else if ( - lowerReason.includes("timeout") || - lowerReason.includes("timed out") - ) { - reason = 20; - } else if (lowerReason.includes("full")) { - reason = 23; - } else { - reason = 10; - } + if (text.toLowerCase().includes("failed to verify username")) { + if (msAuth.hasCachedAccount() && client.useLinkedAccount === false) { + returnToLobby( + 'This server requires a Java Edition account, execute "/lce uselink true"', + ); + } else { + returnToLobby( + "This server requires a Java Edition account, use /lce link", + ); } - proxy.disconnectClient(client, reason); } else { - proxy.disconnectClient(client, 2); + returnToLobby(`${prefix}: ${text}`); } - }); + }; + + javaClient.on("disconnect", (packet) => + handleDisconnectReason(packet.reason, "Disconnected"), + ); + javaClient.on("kick_disconnect", (packet) => + handleDisconnectReason(packet.reason, "Kicked"), + ); javaClient.on("end", () => { - if (proxy.clients.indexOf(client) > -1 && !client._removing) { - proxy.disconnectClient(client, 2); + if (client.javaClient) { + returnToLobby("Connection to server closed"); } }); diff --git a/src/lanbroadcast.js b/src/lanbroadcast.js index 7ae1e0e..3daca49 100644 --- a/src/lanbroadcast.js +++ b/src/lanbroadcast.js @@ -16,13 +16,17 @@ class LANBroadcast { this.buffer.writeUInt16LE(char, 8 + i * 2); } - this.buffer.writeUInt8(1, 72); //player count including host + this.buffer.writeUInt8(1, 72); //player count (updated dynamically) this.buffer.writeUInt8(8, 73); //max players this.buffer.writeUInt32LE(0, 74); //host settings this.buffer.writeUInt32LE(0, 78); //texture pack ID this.buffer.writeUInt8(0, 82); //sub texture pack ID this.buffer.writeUInt8(1, 83); //is joinable } + + setPlayerCount(count) { + this.buffer.writeUInt8(Math.max(1, count), 72); + } } module.exports = LANBroadcast; diff --git a/src/lceproxy.js b/src/lceproxy.js index cd84b56..11a89b2 100644 --- a/src/lceproxy.js +++ b/src/lceproxy.js @@ -11,9 +11,7 @@ const { GAME_PORT, WIN64_LAN_DISCOVERY_PORT, MINECRAFT_NET_VERSION, - USE_LEGACY_USERNAME, - CUSTOM_USERNAME, - SERVERS, + LOBBY_NAME, } = require("../constants"); const PacketWriter = require("./packetwriter"); @@ -185,13 +183,6 @@ class LCEProxy { this.startTCPServer(); console.log("\nProxy is now running!"); - console.log( - `Broadcasting ${SERVERS.length} server(s) as LAN games on Minecraft Legacy Edition.`, - ); - SERVERS.forEach((server, index) => { - const serverStr = typeof server === "string" ? server : server.server; - console.log(` - ${serverStr} on port ${GAME_PORT + index}`); - }); console.log("Press Ctrl+C to stop.\n"); } @@ -201,96 +192,80 @@ class LCEProxy { this.udpSocket.setBroadcast(true); }); - this.broadcasts = SERVERS.map((server, index) => { - const gamePort = GAME_PORT + index; - const serverStr = typeof server === "string" ? server : server.server; - const parts = serverStr.split(":"); - this.serverConfigs[index] = { - host: parts[0], - port: parts[1] ? parseInt(parts[1]) : 25565, - name: serverStr, - cracked: typeof server === "object" ? server.cracked : false, - gamePort: gamePort, - }; - return new LANBroadcast(serverStr, gamePort); - }); + this.lobbyBroadcast = new LANBroadcast(LOBBY_NAME, GAME_PORT); this.broadcastInterval = setInterval(() => { - this.broadcasts.forEach((broadcast) => { - this.udpSocket.send( - broadcast.buffer, - 0, - broadcast.buffer.length, - WIN64_LAN_DISCOVERY_PORT, - "255.255.255.255", - ); - }); + this.lobbyBroadcast.setPlayerCount(Math.max(1, this.clients.length)); + this.udpSocket.send( + this.lobbyBroadcast.buffer, + 0, + this.lobbyBroadcast.buffer.length, + WIN64_LAN_DISCOVERY_PORT, + "255.255.255.255", + ); }, 1000); } startTCPServer() { - this.serverConfigs.forEach((serverConfig, index) => { - const tcpServer = net.createServer((socket) => { - socket.setNoDelay(true); + const tcpServer = net.createServer((socket) => { + socket.setNoDelay(true); - let smallId = 0; - while (this.usedSmallIds.has(smallId) && smallId <= 8) { - smallId++; - } + let smallId = 0; + while (this.usedSmallIds.has(smallId) && smallId <= 8) { + smallId++; + } - if (smallId > 8) { - socket.destroy(); - return; - } + if (smallId > 8) { + socket.destroy(); + return; + } - this.usedSmallIds.add(smallId); + this.usedSmallIds.add(smallId); - const client = { - socket: socket, - smallId: smallId, - buffer: Buffer.alloc(0), - state: "prelogin", - javaClient: null, - breakingBlock: null, - javaPlayers: new Map(), - nextLceEntityId: 100, - javaToLceEntityId: new Map(), - javaEntities: new Map(), - serverConfig: serverConfig, - }; + const client = { + socket: socket, + smallId: smallId, + buffer: Buffer.alloc(0), + state: "prelogin", + javaClient: null, + breakingBlock: null, + javaPlayers: new Map(), + nextLceEntityId: 100, + javaToLceEntityId: new Map(), + javaEntities: new Map(), + serverConfig: null, + inLobby: true, + }; - this.clients.push(client); + this.clients.push(client); - const smallIdBuffer = Buffer.from([smallId]); - socket.write(smallIdBuffer); + const smallIdBuffer = Buffer.from([smallId]); + socket.write(smallIdBuffer); - socket.on("data", (data) => { - this.handleClientData(client, data); - }); - - socket.on("end", () => { - this.removeClient(client); - }); - - socket.on("error", (err) => { - this.removeClient(client); - }); - - socket.on("close", () => { - if (this.clients.indexOf(client) > -1) { - this.removeClient(client); - } - }); + socket.on("data", (data) => { + this.handleClientData(client, data); }); - tcpServer.listen(serverConfig.gamePort, () => { - console.log( - `Server ${serverConfig.name} should now be visible on LCE.`, - ); + socket.on("end", () => { + this.removeClient(client); }); - this.tcpServers.push(tcpServer); + socket.on("error", (err) => { + this.removeClient(client); + }); + + socket.on("close", () => { + if (this.clients.indexOf(client) > -1) { + this.removeClient(client); + } + }); }); + + tcpServer.listen(GAME_PORT, () => { + /* do nothing */ + }); + + this.tcpServers.push(tcpServer); } getPacketDataSize(packetId, data, offset) { @@ -500,9 +475,6 @@ class LCEProxy { this.handlePreLogin(client, packetData); break; case 0xab: //AuthResponsePacket (171) - console.log( - `[AUTH] Received AuthResponsePacket, state=${client.state}, authState=${client.authState}`, - ); if (client.state === "login") { this.handleAuthResponse(client, packetData); } @@ -810,12 +782,11 @@ class LCEProxy { const username = reader.readString(); client.state = "login"; - client.username = !USE_LEGACY_USERNAME ? CUSTOM_USERNAME : username; + client.username = username; if (client.javaClient) { const oldJavaClient = client.javaClient; client.javaClient = null; - oldJavaClient.removeAllListeners(); oldJavaClient.on("error", () => { /* do nothing */ @@ -827,7 +798,109 @@ class LCEProxy { } } - this.connectToJavaServer(client); + this.enterLobby(client); + } + + sendChatToClient(client, text) { + const writer = new PacketWriter(); + writer.writeShort(0); + writer.writeShort(0x10); + writer.writeString(text); + this.sendPacket(client, 0x03, writer.toBuffer()); + } + + enterLobby(client, message) { + client.inLobby = true; + client.javaGameMode = 2; + client.javaDimension = 0; + client.javaSpawnX = 0.5; + client.javaSpawnY = 65.0; + client.javaSpawnZ = 0.5; + client.javaYaw = 0; + client.javaPitch = 0; + client.javaPlayers = new Map(); + client.javaEntities = new Map(); + client.javaToLceEntityId = new Map(); + client.nextLceEntityId = 100; + client.loadedChunks = new Set(); + client.heldSlot = 0; + client.inventory = {}; + client._windowInventories = {}; + client._currentChest = null; + client._deadEntities = new Set(); + client._destroyedEntities = new Set(); + client._dyingPlayers = new Map(); + client._lastJavaPackets = []; + client._bookGUI = null; + client._craftingInProgress = false; + client._pendingAttacks = new Set(); + client._lastAttacks = new Map(); + client._itemUseState = {}; + client.openWindowId = null; + client.openWindowType = null; + client.isRiding = false; + client.abilities = {}; + client.serverConfig = null; + + client.state = "play"; + this.sendLCELoginResponse(client); + + for (const other of this.clients) { + if (other === client || other.state !== "play" || other.inLobby) continue; + this.sendAddPlayerPacket(client, { + entityId: other.smallId, + name: other.username || "Player", + x: other.playerX || 0, + y: other.playerY || 65, + z: other.playerZ || 0, + yaw: other.playerYaw || 0, + pitch: other.playerPitch || 0, + }); + } + + for (const other of this.clients) { + if (other === client || other.state !== "play" || other.inLobby) continue; + this.sendAddPlayerPacket(other, { + entityId: client.smallId, + name: client.username || "Player", + x: 0.5, + y: 65, + z: 0.5, + yaw: 0, + pitch: 0, + }); + } + + for (let cx = -2; cx <= 2; cx++) { + for (let cz = -2; cz <= 2; cz++) { + this.sendFlatChunk(client, cx, cz); + } + } + + const welcomeDelay = message ? 200 : 500; + if (message) { + setTimeout(() => { + this.sendChatToClient(client, message); + }, 200); + } + setTimeout( + () => { + this.sendChatToClient( + client, + "Welcome to LegacyCrossPlay - developed by @DeveloperExotic!", + ); + }, + message ? 600 : 500, + ); + setTimeout( + () => { + this.sendChatToClient( + client, + "Type /lce connect to join a Java Edition server", + ); + }, + message ? 900 : 800, + ); } sendLCELoginResponse(client) { @@ -2132,6 +2205,22 @@ class LCEProxy { if (stringCount > 0) { const message = reader.readString(); + if (message && message.startsWith("/lce ")) { + this.handleCommand(client, message); + return; + } + + if (message && message.startsWith("/")) { + if (client.javaClient && client.javaClient.write) { + try { + client.javaClient.write("chat", { message }); + } catch (err) { + /* do nothing */ + } + } + return; + } + if ( client.javaClient && client.javaClient.write && @@ -2139,9 +2228,7 @@ class LCEProxy { message.length > 0 ) { try { - client.javaClient.write("chat", { - message: message, - }); + client.javaClient.write("chat", { message }); } catch (err) { /* do nothing */ } @@ -2149,6 +2236,168 @@ class LCEProxy { } } + handleCommand(client, message) { + const parts = message.trim().split(/\s+/); + const cmd = parts[1]?.toLowerCase(); + + if (cmd === "connect") { + if (parts.length !== 3) { + this.sendChatToClient(client, "Command not valid"); + return; + } + + const arg = parts[2]; + const colonCount = (arg.match(/:/g) || []).length; + if (colonCount > 1) { + this.sendChatToClient(client, "Command not valid"); + return; + } + + let host, port; + if (colonCount === 1) { + const split = arg.split(":"); + host = split[0]; + port = parseInt(split[1], 10); + if (!host || isNaN(port) || port < 1 || port > 65535) { + this.sendChatToClient(client, "Command not valid"); + return; + } + } else { + host = arg; + port = 25565; + } + + const displayAddr = colonCount === 1 ? arg : `${host}:${port}`; + this.sendChatToClient(client, `Connecting to ${displayAddr}`); + + if (client.javaClient) { + const old = client.javaClient; + client.javaClient = null; + old.removeAllListeners(); + old.on("error", () => { + /* do nothing */ + }); + try { + old.end(); + } catch (e) { + /* do nothing */ + } + } + + client.inLobby = false; + client.state = "login"; + client.serverConfig = { + host, + port, + name: displayAddr, + cracked: !( + client.useLinkedAccount !== false && + require("./auth").hasCachedAccount() + ), + }; + + this.connectToJavaServer(client); + } else if (cmd === "disconnect") { + if (parts.length !== 2) { + this.sendChatToClient(client, "Command not valid"); + return; + } + if (client.inLobby || !client.javaClient) { + this.sendChatToClient(client, "Command not valid"); + return; + } + const old = client.javaClient; + client.javaClient = null; + old.removeAllListeners(); + old.on("error", () => { + /* do nothing */ + }); + try { + old.end(); + } catch (e) { + /* do nothing */ + } + this.enterLobby(client, "Disconnected from server"); + } else if (cmd === "link") { + if (parts.length !== 2) { + this.sendChatToClient(client, "Usage: /lce link"); + return; + } + const msAuth = require("./auth"); + if (msAuth.hasCachedAccount()) { + this.sendChatToClient(client, "You already have an account linked!"); + return; + } + this.sendChatToClient(client, "Starting Microsoft authentication..."); + let linkInterval = null; + msAuth + .authenticate((data) => { + const code = + data.userCode || + data.user_code || + (data.message && data.message.match(/[A-Z0-9]{8,}/)?.[0]) || + "???"; + const msg = `To link your account, head to https://www.microsoft.com/link and enter code ${code}`; + this.sendChatToClient(client, msg); + linkInterval = setInterval( + () => this.sendChatToClient(client, msg), + 1000, + ); + }) + .then(() => { + if (linkInterval) { + clearInterval(linkInterval); + linkInterval = null; + } + this.sendChatToClient( + client, + `Account successfully linked as ${msAuth.username}, you may now reconnect to the server`, + ); + this.sendChatToClient( + client, + "Use /lce unlink to unlink your account", + ); + this.sendChatToClient( + client, + "Or use /lce uselink to toggle using your linked account", + ); + }) + .catch((err) => { + if (linkInterval) { + clearInterval(linkInterval); + linkInterval = null; + } + this.sendChatToClient( + client, + `Failed to link account: ${err.message || err}`, + ); + }); + } else if (cmd === "unlink") { + if (parts.length !== 2) { + this.sendChatToClient(client, "Usage: /lce unlink"); + return; + } + const msAuth = require("./auth"); + msAuth.unlink(); + this.sendChatToClient(client, "Java Edition account unlinked."); + } else if (cmd === "uselink") { + if (parts.length !== 3 || (parts[2] !== "true" && parts[2] !== "false")) { + this.sendChatToClient(client, "Usage: /lce uselink "); + return; + } + client.useLinkedAccount = parts[2] === "true"; + this.sendChatToClient( + client, + `Using linked account: ${client.useLinkedAccount}`, + ); + } else { + this.sendChatToClient( + client, + "Command not valid. Use /lce connect or /lce disconnect", + ); + } + } + handlePlayerAction(client, data, isBundled = false) { const reader = new PacketReader(data.slice(1)); const action = reader.readByte(); @@ -3850,7 +4099,6 @@ class LCEProxy { 23: "Server full", }; const reasonText = reasonMap[reason] || `Unknown (${reason})`; - console.log(`DISCONNECTED: ${reasonText}`); if (client.socket && !client.socket.destroyed) { try { @@ -3926,7 +4174,7 @@ class LCEProxy { try { return await connectToJavaServerFunc(this, client); } catch (err) { - console.log(`DISCONNECTED: ${err}`); + /* do nothing */ } }