From dd2d8ed1cdae4d7b5d97e65ed649916e1b91e10e Mon Sep 17 00:00:00 2001 From: q13x <84165981+WorldEditAxe@users.noreply.github.com> Date: Sun, 2 Jul 2023 23:08:04 -0700 Subject: [PATCH 1/3] ope --- README.md | 4 +- src/plugins/EagProxyAAS/utils.ts | 615 +++++++++++++++++++------------ 2 files changed, 387 insertions(+), 232 deletions(-) diff --git a/README.md b/README.md index 683e9e7..3c2b17f 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,7 @@ Remove all the folders in `src/plugins`. #### IMPORTANT: READ ME BEFORE USING -It is highly suggested that you use [Resent Client](https://reslauncher.vercel.app/) if you aren't already. It provides better performance, FPS, and will allow for a more stable connection to servers. - -**IMPORTANT:** Although both Resent Client and the vanilla Eaglercraft client are safe modified copies of Minecraft AOT-compiled to JavaScript, I cannot guarantee that you **will not get flagged by all anticheats.** While gameplay and testing has shown to be relatively stable and free of anticheat flags, more testing is needed to derive a conclusion on whether or not using EaglerProxy with EagProxyAAS is safe. +**IMPORTANT:** Although the vanilla Eaglercraft client is a safe, modified copy of Minecraft AOT-compiled to JavaScript, I cannot guarantee that you **will not get flagged by all anticheats.** While gameplay and testing has shown to be relatively stable and free of anticheat flags, more testing is needed to derive a conclusion on whether or not using EaglerProxy with EagProxyAAS is safe. EaglerProxy and EagProxyAAS: diff --git a/src/plugins/EagProxyAAS/utils.ts b/src/plugins/EagProxyAAS/utils.ts index cca71cd..1e9c959 100644 --- a/src/plugins/EagProxyAAS/utils.ts +++ b/src/plugins/EagProxyAAS/utils.ts @@ -1,263 +1,420 @@ -import { ServerGlobals } from "./types.js" -import * as Chunk from "prismarine-chunk" -import * as Block from "prismarine-block" -import * as Registry from "prismarine-registry" -import vec3 from "vec3" -import { Client } from "minecraft-protocol" -import { ClientState, ConnectionState } from "./types.js" -import { auth, ServerDeviceCodeResponse } from "./auth.js" +import { ServerGlobals } from "./types.js"; +import * as Chunk from "prismarine-chunk"; +import * as Block from "prismarine-block"; +import * as Registry from "prismarine-registry"; +import vec3 from "vec3"; +import { Client } from "minecraft-protocol"; +import { ClientState, ConnectionState } from "./types.js"; +import { auth, ServerDeviceCodeResponse } from "./auth.js"; -const { Vec3 } = vec3 as any -const Enums = PLUGIN_MANAGER.Enums -const Util = PLUGIN_MANAGER.Util -const MAX_LIFETIME_CONNECTED = 10 * 60 * 1000, MAX_LIFETIME_AUTH = 5 * 60 * 1000, MAX_LIFETIME_LOGIN = 1 * 60 * 1000 -const REGISTRY = Registry.default('1.8.8'), McBlock = (Block as any).default('1.8.8'), LOGIN_CHUNK = generateSpawnChunk().dump() -const logger = new PLUGIN_MANAGER.Logger("PlayerHandler") +const { Vec3 } = vec3 as any; +const Enums = PLUGIN_MANAGER.Enums; +const Util = PLUGIN_MANAGER.Util; +const MAX_LIFETIME_CONNECTED = 10 * 60 * 1000, + MAX_LIFETIME_AUTH = 5 * 60 * 1000, + MAX_LIFETIME_LOGIN = 1 * 60 * 1000; +const REGISTRY = Registry.default("1.8.8"), + McBlock = (Block as any).default("1.8.8"), + LOGIN_CHUNK = generateSpawnChunk().dump(); +const logger = new PLUGIN_MANAGER.Logger("PlayerHandler"); -let SERVER: ServerGlobals = null +let SERVER: ServerGlobals = null; export function setSG(svr: ServerGlobals) { - SERVER = svr + SERVER = svr; } export function disconectIdle() { - SERVER.players.forEach(client => { - if (client.state == ConnectionState.AUTH && (Date.now() - client.lastStatusUpdate) > MAX_LIFETIME_AUTH) { - client.gameClient.end("Timed out waiting for user to login via Microsoft") - } else if (client.state == ConnectionState.SUCCESS && (Date.now() - client.lastStatusUpdate) > MAX_LIFETIME_CONNECTED) { - client.gameClient.end(Enums.ChatColor.RED + "Please enter the IP of the server you'd like to connect to in chat.") - } - }) + SERVER.players.forEach((client) => { + if ( + client.state == ConnectionState.AUTH && + Date.now() - client.lastStatusUpdate > MAX_LIFETIME_AUTH + ) { + client.gameClient.end( + "Timed out waiting for user to login via Microsoft" + ); + } else if ( + client.state == ConnectionState.SUCCESS && + Date.now() - client.lastStatusUpdate > MAX_LIFETIME_CONNECTED + ) { + client.gameClient.end( + Enums.ChatColor.RED + + "Please enter the IP of the server you'd like to connect to in chat." + ); + } + }); } export function handleConnect(client: ClientState) { - client.gameClient.write('login', { - entityId: 1, - gameMode: 2, - dimension: 0, - difficulty: 1, - maxPlayers: 1, - levelType: 'flat', - reducedDebugInfo: false - }) - client.gameClient.write('map_chunk', { - x: 0, - z: 0, - groundUp: true, - bitMap: 0xFFFF, - chunkData: LOGIN_CHUNK - }) - client.gameClient.write('position', { - x: 0, - y: 65, - z: 8.5, - yaw: -90, - pitch: 0, - flags: 0x01 - }) + client.gameClient.write("login", { + entityId: 1, + gameMode: 2, + dimension: 0, + difficulty: 1, + maxPlayers: 1, + levelType: "flat", + reducedDebugInfo: false, + }); + client.gameClient.write("map_chunk", { + x: 0, + z: 0, + groundUp: true, + bitMap: 0xffff, + chunkData: LOGIN_CHUNK, + }); + client.gameClient.write("position", { + x: 0, + y: 65, + z: 8.5, + yaw: -90, + pitch: 0, + flags: 0x01, + }); - client.gameClient.write('playerlist_header', { - header: JSON.stringify({ - text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server ` - }), - footer: JSON.stringify({ - text: `${Enums.ChatColor.GOLD}Please wait for instructions.` - }) - }) - onConnect(client) + client.gameClient.write("playerlist_header", { + header: JSON.stringify({ + text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, + }), + footer: JSON.stringify({ + text: `${Enums.ChatColor.GOLD}Please wait for instructions.`, + }), + }); + onConnect(client); } -export function awaitCommand(client: Client, filter: (msg: string) => boolean): Promise { - return new Promise((res, rej) => { - const onMsg = packet => { - if (filter(packet.message)) { - client.removeListener('chat', onMsg) - client.removeListener('end', onEnd) - res(packet.message) - } - } - const onEnd = () => rej("Client disconnected before promise could be resolved") - client.on('chat', onMsg) - client.on('end', onEnd) - }) +export function awaitCommand( + client: Client, + filter: (msg: string) => boolean +): Promise { + return new Promise((res, rej) => { + const onMsg = (packet) => { + if (filter(packet.message)) { + client.removeListener("chat", onMsg); + client.removeListener("end", onEnd); + res(packet.message); + } + }; + const onEnd = () => + rej("Client disconnected before promise could be resolved"); + client.on("chat", onMsg); + client.on("end", onEnd); + }); } export function sendMessage(client: Client, msg: string) { - client.write('chat', { - message: JSON.stringify({ text: msg }), - position: 1 - }) + client.write("chat", { + message: JSON.stringify({ text: msg }), + position: 1, + }); } export function sendMessageWarning(client: Client, msg: string) { - client.write('chat', { - message: JSON.stringify({ - text: msg, - color: 'yellow' - }), - position: 1 - }) + client.write("chat", { + message: JSON.stringify({ + text: msg, + color: "yellow", + }), + position: 1, + }); } export function sendMessageLogin(client: Client, url: string, token: string) { - client.write('chat', { - message: JSON.stringify({ - text: "Please go to ", - color: Enums.ChatColor.RESET, - extra: [ - { - text: url, - color: 'gold', - clickEvent: { - action: "open_url", - value: url - }, - hoverEvent: { - action: "show_text", - value: Enums.ChatColor.GOLD + "Click to open me in a new window!" - } - }, - { - text: " and login via the code " - }, - { - text: token, - color: 'gold', - hoverEvent: { - action: "show_text", - value: Enums.ChatColor.GOLD + "Click me to copy to chat to copy from there!" - }, - clickEvent: { - action: "suggest_command", - value: token - } - }, - { - text: "." - } - ] - }), - position: 1 - }) + client.write("chat", { + message: JSON.stringify({ + text: "Please go to ", + color: Enums.ChatColor.RESET, + extra: [ + { + text: url, + color: "gold", + clickEvent: { + action: "open_url", + value: url, + }, + hoverEvent: { + action: "show_text", + value: Enums.ChatColor.GOLD + "Click to open me in a new window!", + }, + }, + { + text: " and login via the code ", + }, + { + text: token, + color: "gold", + hoverEvent: { + action: "show_text", + value: + Enums.ChatColor.GOLD + + "Click me to copy to chat to copy from there!", + }, + clickEvent: { + action: "suggest_command", + value: token, + }, + }, + { + text: ".", + }, + ], + }), + position: 1, + }); } -export function updateState(client: Client, newState: 'AUTH' | 'SERVER', uri?: string, code?: string) { - switch(newState) { - case 'AUTH': - if (code == null || uri == null) throw new Error("Missing code/uri required for title message type AUTH") - client.write('playerlist_header', { - header: JSON.stringify({ - text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server ` - }), - footer: JSON.stringify({ - text: `${Enums.ChatColor.RED}${uri}${Enums.ChatColor.GOLD} | Code: ${Enums.ChatColor.RED}${code}` - }) - }) - break - case 'SERVER': - client.write('playerlist_header', { - header: JSON.stringify({ - text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server ` - }), - footer: JSON.stringify({ - text: `${Enums.ChatColor.RED}/join [port]` - }) - }) - break - } +export function updateState( + client: Client, + newState: "AUTH" | "SERVER", + uri?: string, + code?: string +) { + switch (newState) { + case "AUTH": + if (code == null || uri == null) + throw new Error( + "Missing code/uri required for title message type AUTH" + ); + client.write("playerlist_header", { + header: JSON.stringify({ + text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, + }), + footer: JSON.stringify({ + text: `${Enums.ChatColor.RED}${uri}${Enums.ChatColor.GOLD} | Code: ${Enums.ChatColor.RED}${code}`, + }), + }); + break; + case "SERVER": + client.write("playerlist_header", { + header: JSON.stringify({ + text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, + }), + footer: JSON.stringify({ + text: `${Enums.ChatColor.RED}/join [port]`, + }), + }); + break; + } } export async function onConnect(client: ClientState) { - try { - client.state = ConnectionState.AUTH - client.lastStatusUpdate = Date.now() + try { + client.state = ConnectionState.AUTH; + client.lastStatusUpdate = Date.now(); - sendMessageWarning(client.gameClient, `WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.`) - await new Promise(res => setTimeout(res, 2000)) - sendMessageWarning(client.gameClient, `WARNING: It is highly suggested that you turn down settings and use Resent Client, as gameplay tends to be very laggy and unplayable on low powered devices.`) - await new Promise(res => setTimeout(res, 2000)) - sendMessageWarning(client.gameClient, `WARNING: You will be prompted to log in via Microsoft to obtain a session token necessary to join games. Any data related to your account will not be saved and for transparency reasons this proxy's source code is available on Github.`) - await new Promise(res => setTimeout(res, 2000)) + sendMessageWarning( + client.gameClient, + `WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.` + ); + await new Promise((res) => setTimeout(res, 2000)); + sendMessageWarning( + client.gameClient, + `WARNING: It is highly suggested that you turn down settings, as gameplay tends to be very laggy and unplayable on low powered devices.` + ); + await new Promise((res) => setTimeout(res, 2000)); + sendMessageWarning( + client.gameClient, + `WARNING: You will be prompted to log in via Microsoft to obtain a session token necessary to join games. Any data related to your account will not be saved and for transparency reasons this proxy's source code is available on Github.` + ); + await new Promise((res) => setTimeout(res, 2000)); - client.lastStatusUpdate = Date.now() - let errored = false, savedAuth - const authHandler = auth(), codeCallback = (code: ServerDeviceCodeResponse) => { - updateState(client.gameClient, 'AUTH', code.verification_uri, code.user_code) - sendMessageLogin(client.gameClient, code.verification_uri, code.user_code) - } - authHandler.once('error', err => { - if (!client.gameClient.ended) client.gameClient.end(err.message) - errored = true - }) - if (errored) return - authHandler.on('code', codeCallback) - await new Promise(res => authHandler.once('done', result => { - savedAuth = result - res(result) - })) - sendMessage(client.gameClient, Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!") + client.lastStatusUpdate = Date.now(); + let errored = false, + savedAuth; + const authHandler = auth(), + codeCallback = (code: ServerDeviceCodeResponse) => { + updateState( + client.gameClient, + "AUTH", + code.verification_uri, + code.user_code + ); + sendMessageLogin( + client.gameClient, + code.verification_uri, + code.user_code + ); + }; + authHandler.once("error", (err) => { + if (!client.gameClient.ended) client.gameClient.end(err.message); + errored = true; + }); + if (errored) return; + authHandler.on("code", codeCallback); + await new Promise((res) => + authHandler.once("done", (result) => { + savedAuth = result; + res(result); + }) + ); + sendMessage( + client.gameClient, + Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!" + ); - client.state = ConnectionState.SUCCESS - client.lastStatusUpdate = Date.now() - updateState(client.gameClient, 'SERVER') - sendMessage(client.gameClient, `Provide a server to join. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.`) - let host: string, port: number - while (true) { - const msg = await awaitCommand(client.gameClient, msg => msg.startsWith("/join")), parsed = msg.split(/ /gi, 3) - if (parsed.length < 2) sendMessage(client.gameClient, `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.`) - else if (parsed.length > 3 && isNaN(parseInt(parsed[2]))) sendMessage(client.gameClient, `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.`) - else { - host = parsed[1] - if (parsed.length > 3) port = parseInt(parsed[2]) - port = port ?? 25565 - break - } - } - try { - await PLUGIN_MANAGER.proxy.players.get(client.gameClient.username).switchServers({ - host: host, - port: port, - version: "1.8.8", - username: savedAuth.selectedProfile.name, - auth: 'mojang', - keepAlive: false, - session: { - accessToken: savedAuth.accessToken, - clientToken: savedAuth.selectedProfile.id, - selectedProfile: { - id: savedAuth.selectedProfile.id, - name: savedAuth.selectedProfile.name - } - }, - skipValidation: true, - hideErrors: true - }) - } catch (err) { - if (!client.gameClient.ended) { - client.gameClient.end(Enums.ChatColor.RED + `Something went wrong whilst switching servers: ${err.message}`) - } - } - } catch (err) { - if (!client.gameClient.ended) { - logger.error(`Error whilst processing user ${client.gameClient.username}: ${err.stack || err}`) - client.gameClient.end(Enums.ChatColor.YELLOW + "Something went wrong whilst processing your request. Please reconnect.") - } + client.state = ConnectionState.SUCCESS; + client.lastStatusUpdate = Date.now(); + updateState(client.gameClient, "SERVER"); + sendMessage( + client.gameClient, + `Provide a server to join. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` + ); + let host: string, port: number; + while (true) { + const msg = await awaitCommand(client.gameClient, (msg) => + msg.startsWith("/join") + ), + parsed = msg.split(/ /gi, 3); + if (parsed.length < 2) + sendMessage( + client.gameClient, + `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` + ); + else if (parsed.length > 3 && isNaN(parseInt(parsed[2]))) + sendMessage( + client.gameClient, + `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` + ); + else { + host = parsed[1]; + if (parsed.length > 3) port = parseInt(parsed[2]); + port = port ?? 25565; + break; + } } + try { + await PLUGIN_MANAGER.proxy.players + .get(client.gameClient.username) + .switchServers({ + host: host, + port: port, + version: "1.8.8", + username: savedAuth.selectedProfile.name, + auth: "mojang", + keepAlive: false, + session: { + accessToken: savedAuth.accessToken, + clientToken: savedAuth.selectedProfile.id, + selectedProfile: { + id: savedAuth.selectedProfile.id, + name: savedAuth.selectedProfile.name, + }, + }, + skipValidation: true, + hideErrors: true, + }); + } catch (err) { + if (!client.gameClient.ended) { + client.gameClient.end( + Enums.ChatColor.RED + + `Something went wrong whilst switching servers: ${err.message}` + ); + } + } + } catch (err) { + if (!client.gameClient.ended) { + logger.error( + `Error whilst processing user ${client.gameClient.username}: ${ + err.stack || err + }` + ); + client.gameClient.end( + Enums.ChatColor.YELLOW + + "Something went wrong whilst processing your request. Please reconnect." + ); + } + } } export function generateSpawnChunk(): Chunk.PCChunk { - const chunk = new (Chunk.default(REGISTRY))(null) as Chunk.PCChunk - chunk.initialize(() => new McBlock(REGISTRY.blocksByName.air.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 64, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 67, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(7, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(7, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(9, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(9, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 65, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 66, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 65, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 66, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setSkyLight(new Vec3(8, 66, 8), 15) - return chunk -} \ No newline at end of file + const chunk = new (Chunk.default(REGISTRY))(null) as Chunk.PCChunk; + chunk.initialize( + () => + new McBlock( + REGISTRY.blocksByName.air.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 64, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 67, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(7, 65, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(7, 66, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(9, 65, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(9, 66, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 65, 7), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 66, 7), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 65, 9), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 66, 9), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setSkyLight(new Vec3(8, 66, 8), 15); + return chunk; +} From 4915b182a8aaa350aa992047828a0e7c7efaa194 Mon Sep 17 00:00:00 2001 From: q13x <84165981+WorldEditAxe@users.noreply.github.com> Date: Sun, 2 Jul 2023 23:08:04 -0700 Subject: [PATCH 2/3] ope --- README.md | 4 +- src/plugins/EagProxyAAS/utils.ts | 644 +++++++++++++++++++------------ 2 files changed, 388 insertions(+), 260 deletions(-) diff --git a/README.md b/README.md index ef7054f..d621d76 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,7 @@ Remove all the folders in `src/plugins`. #### IMPORTANT: READ ME BEFORE USING -It is highly suggested that you use [Resent Client](https://reslauncher.vercel.app/) if you aren't already. It provides better performance, FPS, and will allow for a more stable connection to servers. - -**IMPORTANT:** Although both Resent Client and the vanilla Eaglercraft client are safe modified copies of Minecraft AOT-compiled to JavaScript, I cannot guarantee that you **will not get flagged by all anticheats.** While gameplay and testing has shown to be relatively stable and free of anticheat flags, more testing is needed to derive a conclusion on whether or not using EaglerProxy with EagProxyAAS is safe. +**IMPORTANT:** Although the vanilla Eaglercraft client is a safe, modified copy of Minecraft AOT-compiled to JavaScript, I cannot guarantee that you **will not get flagged by all anticheats.** While gameplay and testing has shown to be relatively stable and free of anticheat flags, more testing is needed to derive a conclusion on whether or not using EaglerProxy with EagProxyAAS is safe. EaglerProxy and EagProxyAAS: diff --git a/src/plugins/EagProxyAAS/utils.ts b/src/plugins/EagProxyAAS/utils.ts index c77ff16..1e9c959 100644 --- a/src/plugins/EagProxyAAS/utils.ts +++ b/src/plugins/EagProxyAAS/utils.ts @@ -1,290 +1,420 @@ -import { ServerGlobals } from "./types.js" -import * as Chunk from "prismarine-chunk" -import * as Block from "prismarine-block" -import * as Registry from "prismarine-registry" -import vec3 from "vec3" -import { Client } from "minecraft-protocol" -import { ClientState, ConnectionState } from "./types.js" -import { auth, ServerDeviceCodeResponse } from "./auth.js" +import { ServerGlobals } from "./types.js"; +import * as Chunk from "prismarine-chunk"; +import * as Block from "prismarine-block"; +import * as Registry from "prismarine-registry"; +import vec3 from "vec3"; +import { Client } from "minecraft-protocol"; +import { ClientState, ConnectionState } from "./types.js"; +import { auth, ServerDeviceCodeResponse } from "./auth.js"; -const { Vec3 } = vec3 as any -const Enums = PLUGIN_MANAGER.Enums -const Util = PLUGIN_MANAGER.Util -const MAX_LIFETIME_CONNECTED = 10 * 60 * 1000, MAX_LIFETIME_AUTH = 5 * 60 * 1000, MAX_LIFETIME_LOGIN = 1 * 60 * 1000 -const REGISTRY = Registry.default('1.8.8'), McBlock = (Block as any).default('1.8.8'), LOGIN_CHUNK = generateSpawnChunk().dump() -const logger = new PLUGIN_MANAGER.Logger("PlayerHandler") +const { Vec3 } = vec3 as any; +const Enums = PLUGIN_MANAGER.Enums; +const Util = PLUGIN_MANAGER.Util; +const MAX_LIFETIME_CONNECTED = 10 * 60 * 1000, + MAX_LIFETIME_AUTH = 5 * 60 * 1000, + MAX_LIFETIME_LOGIN = 1 * 60 * 1000; +const REGISTRY = Registry.default("1.8.8"), + McBlock = (Block as any).default("1.8.8"), + LOGIN_CHUNK = generateSpawnChunk().dump(); +const logger = new PLUGIN_MANAGER.Logger("PlayerHandler"); -const FORK_URL = "https://repl.it/github/WorldEditAxe/eaglerproxy" - -let SERVER: ServerGlobals = null +let SERVER: ServerGlobals = null; export function setSG(svr: ServerGlobals) { - SERVER = svr + SERVER = svr; } export function disconectIdle() { - SERVER.players.forEach(client => { - if (client.state == ConnectionState.AUTH && (Date.now() - client.lastStatusUpdate) > MAX_LIFETIME_AUTH) { - client.gameClient.end("Timed out waiting for user to login via Microsoft") - } else if (client.state == ConnectionState.SUCCESS && (Date.now() - client.lastStatusUpdate) > MAX_LIFETIME_CONNECTED) { - client.gameClient.end(Enums.ChatColor.RED + "Please enter the IP of the server you'd like to connect to in chat.") - } - }) + SERVER.players.forEach((client) => { + if ( + client.state == ConnectionState.AUTH && + Date.now() - client.lastStatusUpdate > MAX_LIFETIME_AUTH + ) { + client.gameClient.end( + "Timed out waiting for user to login via Microsoft" + ); + } else if ( + client.state == ConnectionState.SUCCESS && + Date.now() - client.lastStatusUpdate > MAX_LIFETIME_CONNECTED + ) { + client.gameClient.end( + Enums.ChatColor.RED + + "Please enter the IP of the server you'd like to connect to in chat." + ); + } + }); } export function handleConnect(client: ClientState) { - client.gameClient.write('login', { - entityId: 1, - gameMode: 2, - dimension: 0, - difficulty: 1, - maxPlayers: 1, - levelType: 'flat', - reducedDebugInfo: false - }) - client.gameClient.write('map_chunk', { - x: 0, - z: 0, - groundUp: true, - bitMap: 0xFFFF, - chunkData: LOGIN_CHUNK - }) - client.gameClient.write('position', { - x: 0, - y: 65, - z: 8.5, - yaw: -90, - pitch: 0, - flags: 0x01 - }) + client.gameClient.write("login", { + entityId: 1, + gameMode: 2, + dimension: 0, + difficulty: 1, + maxPlayers: 1, + levelType: "flat", + reducedDebugInfo: false, + }); + client.gameClient.write("map_chunk", { + x: 0, + z: 0, + groundUp: true, + bitMap: 0xffff, + chunkData: LOGIN_CHUNK, + }); + client.gameClient.write("position", { + x: 0, + y: 65, + z: 8.5, + yaw: -90, + pitch: 0, + flags: 0x01, + }); - client.gameClient.write('playerlist_header', { - header: JSON.stringify({ - text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server ` - }), - footer: JSON.stringify({ - text: `${Enums.ChatColor.GOLD}Please wait for instructions.` - }) - }) - onConnect(client) + client.gameClient.write("playerlist_header", { + header: JSON.stringify({ + text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, + }), + footer: JSON.stringify({ + text: `${Enums.ChatColor.GOLD}Please wait for instructions.`, + }), + }); + onConnect(client); } -export function awaitCommand(client: Client, filter: (msg: string) => boolean): Promise { - return new Promise((res, rej) => { - const onMsg = packet => { - if (filter(packet.message)) { - client.removeListener('chat', onMsg) - client.removeListener('end', onEnd) - res(packet.message) - } - } - const onEnd = () => rej("Client disconnected before promise could be resolved") - client.on('chat', onMsg) - client.on('end', onEnd) - }) +export function awaitCommand( + client: Client, + filter: (msg: string) => boolean +): Promise { + return new Promise((res, rej) => { + const onMsg = (packet) => { + if (filter(packet.message)) { + client.removeListener("chat", onMsg); + client.removeListener("end", onEnd); + res(packet.message); + } + }; + const onEnd = () => + rej("Client disconnected before promise could be resolved"); + client.on("chat", onMsg); + client.on("end", onEnd); + }); } -export function sendMessage(client: Client, msg: string, json?: any) { - client.write('chat', { - message: JSON.stringify({ text: msg, ...json }), - position: 1 - }) +export function sendMessage(client: Client, msg: string) { + client.write("chat", { + message: JSON.stringify({ text: msg }), + position: 1, + }); } export function sendMessageWarning(client: Client, msg: string) { - client.write('chat', { - message: JSON.stringify({ - text: msg, - color: 'yellow' - }), - position: 1 - }) + client.write("chat", { + message: JSON.stringify({ + text: msg, + color: "yellow", + }), + position: 1, + }); } export function sendMessageLogin(client: Client, url: string, token: string) { - client.write('chat', { - message: JSON.stringify({ - text: "Please go to ", - color: Enums.ChatColor.RESET, - extra: [ - { - text: url, - color: 'gold', - clickEvent: { - action: "open_url", - value: url - }, - hoverEvent: { - action: "show_text", - value: Enums.ChatColor.GOLD + "Click to open me in a new window!" - } - }, - { - text: " and login via the code " - }, - { - text: token, - color: 'gold', - hoverEvent: { - action: "show_text", - value: Enums.ChatColor.GOLD + "Click me to copy to chat!" - }, - clickEvent: { - action: "suggest_command", - value: token - } - }, - { - text: "." - } - ] + client.write("chat", { + message: JSON.stringify({ + text: "Please go to ", + color: Enums.ChatColor.RESET, + extra: [ + { + text: url, + color: "gold", + clickEvent: { + action: "open_url", + value: url, + }, + hoverEvent: { + action: "show_text", + value: Enums.ChatColor.GOLD + "Click to open me in a new window!", + }, + }, + { + text: " and login via the code ", + }, + { + text: token, + color: "gold", + hoverEvent: { + action: "show_text", + value: + Enums.ChatColor.GOLD + + "Click me to copy to chat to copy from there!", + }, + clickEvent: { + action: "suggest_command", + value: token, + }, + }, + { + text: ".", + }, + ], + }), + position: 1, + }); +} + +export function updateState( + client: Client, + newState: "AUTH" | "SERVER", + uri?: string, + code?: string +) { + switch (newState) { + case "AUTH": + if (code == null || uri == null) + throw new Error( + "Missing code/uri required for title message type AUTH" + ); + client.write("playerlist_header", { + header: JSON.stringify({ + text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, }), - position: 1 - }) -} - -export function sendMessageFork(client: Client, url: string) { - client.write('chat', { - message: JSON.stringify({ - text: "Fork me here: ", - color: 'aqua', - extra: [{ - text: "", - color: 'green', - clickEvent: { - action: "open_url", - value: url - }, - hoverEvent: { - action: "show_text", - value: Enums.ChatColor.GOLD + "Click me to open in a new window!" - } - }] - }) - }) -} - -export function updateState(client: Client, newState: 'AUTH' | 'SERVER', uri?: string, code?: string) { - switch(newState) { - case 'AUTH': - if (code == null || uri == null) throw new Error("Missing code/uri required for title message type AUTH") - client.write('playerlist_header', { - header: JSON.stringify({ - text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server ` - }), - footer: JSON.stringify({ - text: `${Enums.ChatColor.RED}${uri}${Enums.ChatColor.GOLD} | Code: ${Enums.ChatColor.RED}${code}` - }) - }) - break - case 'SERVER': - client.write('playerlist_header', { - header: JSON.stringify({ - text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server ` - }), - footer: JSON.stringify({ - text: `${Enums.ChatColor.RED}/join [port]` - }) - }) - break - } + footer: JSON.stringify({ + text: `${Enums.ChatColor.RED}${uri}${Enums.ChatColor.GOLD} | Code: ${Enums.ChatColor.RED}${code}`, + }), + }); + break; + case "SERVER": + client.write("playerlist_header", { + header: JSON.stringify({ + text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, + }), + footer: JSON.stringify({ + text: `${Enums.ChatColor.RED}/join [port]`, + }), + }); + break; + } } export async function onConnect(client: ClientState) { - try { - client.state = ConnectionState.AUTH - client.lastStatusUpdate = Date.now() + try { + client.state = ConnectionState.AUTH; + client.lastStatusUpdate = Date.now(); - sendMessageWarning(client.gameClient, `This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.`) - await new Promise(res => setTimeout(res, 2000)) - sendMessageWarning(client.gameClient, `You will be prompted to log in via Microsoft to obtain a session token necessary to join games. Any data related to your account will not be saved and for transparency reasons this proxy's source code is available on Github. Using any untrusted proxies may put your account at risk.`) - sendMessageFork(client.gameClient, FORK_URL) - await new Promise(res => setTimeout(res, 2000)) + sendMessageWarning( + client.gameClient, + `WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.` + ); + await new Promise((res) => setTimeout(res, 2000)); + sendMessageWarning( + client.gameClient, + `WARNING: It is highly suggested that you turn down settings, as gameplay tends to be very laggy and unplayable on low powered devices.` + ); + await new Promise((res) => setTimeout(res, 2000)); + sendMessageWarning( + client.gameClient, + `WARNING: You will be prompted to log in via Microsoft to obtain a session token necessary to join games. Any data related to your account will not be saved and for transparency reasons this proxy's source code is available on Github.` + ); + await new Promise((res) => setTimeout(res, 2000)); - client.lastStatusUpdate = Date.now() - let errored = false, savedAuth - const authHandler = auth(), codeCallback = (code: ServerDeviceCodeResponse) => { - updateState(client.gameClient, 'AUTH', code.verification_uri, code.user_code) - sendMessageLogin(client.gameClient, code.verification_uri, code.user_code) - } - authHandler.once('error', err => { - if (!client.gameClient.ended) client.gameClient.end(err.message) - errored = true - }) - if (errored) return - authHandler.on('code', codeCallback) - await new Promise(res => authHandler.once('done', result => { - savedAuth = result - res(result) - })) - sendMessage(client.gameClient, Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!") + client.lastStatusUpdate = Date.now(); + let errored = false, + savedAuth; + const authHandler = auth(), + codeCallback = (code: ServerDeviceCodeResponse) => { + updateState( + client.gameClient, + "AUTH", + code.verification_uri, + code.user_code + ); + sendMessageLogin( + client.gameClient, + code.verification_uri, + code.user_code + ); + }; + authHandler.once("error", (err) => { + if (!client.gameClient.ended) client.gameClient.end(err.message); + errored = true; + }); + if (errored) return; + authHandler.on("code", codeCallback); + await new Promise((res) => + authHandler.once("done", (result) => { + savedAuth = result; + res(result); + }) + ); + sendMessage( + client.gameClient, + Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!" + ); - client.state = ConnectionState.SUCCESS - client.lastStatusUpdate = Date.now() - updateState(client.gameClient, 'SERVER') - sendMessage(client.gameClient, `Provide a server to join. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.`) - sendMessage(client.gameClient, "(providing a custom port to a SRV record server domain may potentially cause issues)", { color: "gray" }) - let host: string, port: number - while (true) { - const msg = await awaitCommand(client.gameClient, msg => msg.startsWith("/join")), parsed = msg.split(/ /gi, 3) - if (parsed.length < 2) sendMessage(client.gameClient, `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.`) - else if (parsed.length > 3 && isNaN(parseInt(parsed[2]))) sendMessage(client.gameClient, `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.`) - else { - host = parsed[1] - if (parsed.length >= 3) port = parseInt(parsed[2]) - port = port ?? 25565 - - try { - await PLUGIN_MANAGER.proxy.players.get(client.gameClient.username).switchServers({ - host: host, - port: port, - version: "1.8.8", - username: savedAuth.selectedProfile.name, - auth: 'mojang', - keepAlive: false, - session: { - accessToken: savedAuth.accessToken, - clientToken: savedAuth.selectedProfile.id, - selectedProfile: { - id: savedAuth.selectedProfile.id, - name: savedAuth.selectedProfile.name - } - }, - skipValidation: true, - hideErrors: true - }) - break - } catch (err) { - if (!client.gameClient.ended) { - sendMessage(client.gameClient, `Something went wrong whilst switching servers: ${err.message}.`, { color: "red" }) - if (err.code == "ENOTFOUND") { - sendMessage(client.gameClient, `Please check that the provided IP/hostname is correct, and try again.`, { color: "red" }) - } - } - } - } - } - } catch (err) { - if (!client.gameClient.ended) { - logger.error(`Error whilst processing user ${client.gameClient.username}: ${err.stack || err}`) - client.gameClient.end(Enums.ChatColor.YELLOW + "Something went wrong whilst processing your request. Please reconnect.") - } + client.state = ConnectionState.SUCCESS; + client.lastStatusUpdate = Date.now(); + updateState(client.gameClient, "SERVER"); + sendMessage( + client.gameClient, + `Provide a server to join. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` + ); + let host: string, port: number; + while (true) { + const msg = await awaitCommand(client.gameClient, (msg) => + msg.startsWith("/join") + ), + parsed = msg.split(/ /gi, 3); + if (parsed.length < 2) + sendMessage( + client.gameClient, + `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` + ); + else if (parsed.length > 3 && isNaN(parseInt(parsed[2]))) + sendMessage( + client.gameClient, + `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` + ); + else { + host = parsed[1]; + if (parsed.length > 3) port = parseInt(parsed[2]); + port = port ?? 25565; + break; + } } + try { + await PLUGIN_MANAGER.proxy.players + .get(client.gameClient.username) + .switchServers({ + host: host, + port: port, + version: "1.8.8", + username: savedAuth.selectedProfile.name, + auth: "mojang", + keepAlive: false, + session: { + accessToken: savedAuth.accessToken, + clientToken: savedAuth.selectedProfile.id, + selectedProfile: { + id: savedAuth.selectedProfile.id, + name: savedAuth.selectedProfile.name, + }, + }, + skipValidation: true, + hideErrors: true, + }); + } catch (err) { + if (!client.gameClient.ended) { + client.gameClient.end( + Enums.ChatColor.RED + + `Something went wrong whilst switching servers: ${err.message}` + ); + } + } + } catch (err) { + if (!client.gameClient.ended) { + logger.error( + `Error whilst processing user ${client.gameClient.username}: ${ + err.stack || err + }` + ); + client.gameClient.end( + Enums.ChatColor.YELLOW + + "Something went wrong whilst processing your request. Please reconnect." + ); + } + } } export function generateSpawnChunk(): Chunk.PCChunk { - const chunk = new (Chunk.default(REGISTRY))(null) as Chunk.PCChunk - chunk.initialize(() => new McBlock(REGISTRY.blocksByName.air.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 64, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 67, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(7, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(7, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(9, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(9, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 65, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 66, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 65, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setBlock(new Vec3(8, 66, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.plains.id, 0)) - chunk.setSkyLight(new Vec3(8, 66, 8), 15) - return chunk + const chunk = new (Chunk.default(REGISTRY))(null) as Chunk.PCChunk; + chunk.initialize( + () => + new McBlock( + REGISTRY.blocksByName.air.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 64, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 67, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(7, 65, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(7, 66, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(9, 65, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(9, 66, 8), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 65, 7), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 66, 7), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 65, 9), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setBlock( + new Vec3(8, 66, 9), + new McBlock( + REGISTRY.blocksByName.barrier.id, + REGISTRY.biomesByName.plains.id, + 0 + ) + ); + chunk.setSkyLight(new Vec3(8, 66, 8), 15); + return chunk; } From e6baf8d60a3ec4f413a403ff5eb90b0de8bef5d0 Mon Sep 17 00:00:00 2001 From: q13x <84165981+WorldEditAxe@users.noreply.github.com> Date: Mon, 3 Jul 2023 00:24:45 -0700 Subject: [PATCH 3/3] add offline server support to EagProxyAAS --- README.md | 3 +- src/plugins/EagProxyAAS/config.ts | 7 +- src/plugins/EagProxyAAS/types.ts | 77 ++-- src/plugins/EagProxyAAS/utils.ts | 400 +++++++++++++++----- src/proxy/Player.ts | 610 +++++++++++++++++------------- 5 files changed, 705 insertions(+), 392 deletions(-) diff --git a/README.md b/README.md index d621d76..61ff420 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ As of right now, there only exists one plugin: EagProxyAAS (read below for more EagProxyAAS aims to allow any Eaglercraft client to connect to a normal 1.8.9 Minecraft server (includes Hypixel), provided that players own a legitimate Minecraft Java copy. -_Demo server: `wss://eaglerproxy.q13x.com/` (not being hosted w/ Replit due to data transfer limits)_ +_Demo server: `wss://eaglerproxy.q13x.com/` (not being hosted w/ Replit due to data transfer limits)_ #### I don't want to use this plugin! @@ -48,7 +48,6 @@ EaglerProxy and EagProxyAAS: EaglerProxy and EagProxyAAS does NOT: - include any Microsoft/Mojang code, -- ship with offline (cracked) support by default, - store or otherwise use authentication data for any other purpose as listed on the README, - Unmodified versions will not maliciously handle your login data, although a modified version has the ability to do so. Only use trusted and unmodified versions of both this plugin and proxy. - and intentionally put your account at risk. diff --git a/src/plugins/EagProxyAAS/config.ts b/src/plugins/EagProxyAAS/config.ts index ad1bdca..c3a2951 100644 --- a/src/plugins/EagProxyAAS/config.ts +++ b/src/plugins/EagProxyAAS/config.ts @@ -1,4 +1,5 @@ export const config = { - bindInternalServerPort: 25569, - bindInternalServerIp: "127.0.0.1" -} \ No newline at end of file + bindInternalServerPort: 25569, + bindInternalServerIp: "127.0.0.1", + allowCustomPorts: false, +}; diff --git a/src/plugins/EagProxyAAS/types.ts b/src/plugins/EagProxyAAS/types.ts index cf1eee2..4bdfd1f 100644 --- a/src/plugins/EagProxyAAS/types.ts +++ b/src/plugins/EagProxyAAS/types.ts @@ -1,45 +1,50 @@ -import { Client, Server } from "minecraft-protocol" +import { Client, Server } from "minecraft-protocol"; export type ServerGlobals = { - server: Server, - players: Map -} + server: Server; + players: Map; +}; export type ClientState = { - state: ConnectionState, - gameClient: Client, - token?: string, - lastStatusUpdate: number -} + state: ConnectionState; + gameClient: Client; + token?: string; + lastStatusUpdate: number; +}; export enum ConnectionState { - AUTH, - SUCCESS, - DISCONNECTED + AUTH, + SUCCESS, + DISCONNECTED, } export enum ChatColor { - BLACK = "§0", - DARK_BLUE = "§1", - DARK_GREEN = "§2", - DARK_CYAN = "§3", - DARK_RED = "§4", - PURPLE = "§5", - GOLD = "§6", - GRAY = "§7", - DARK_GRAY = "§8", - BLUE = "§9", - BRIGHT_GREEN = "§a", - CYAN = "§b", - RED = "§c", - PINK = "§d", - YELLOW = "§e", - WHITE = "§f", - // text styling - OBFUSCATED = '§k', - BOLD = '§l', - STRIKETHROUGH = '§m', - UNDERLINED = '§n', - ITALIC = '§o', - RESET = '§r' -} \ No newline at end of file + BLACK = "§0", + DARK_BLUE = "§1", + DARK_GREEN = "§2", + DARK_CYAN = "§3", + DARK_RED = "§4", + PURPLE = "§5", + GOLD = "§6", + GRAY = "§7", + DARK_GRAY = "§8", + BLUE = "§9", + BRIGHT_GREEN = "§a", + CYAN = "§b", + RED = "§c", + PINK = "§d", + YELLOW = "§e", + WHITE = "§f", + // text styling + OBFUSCATED = "§k", + BOLD = "§l", + STRIKETHROUGH = "§m", + UNDERLINED = "§n", + ITALIC = "§o", + RESET = "§r", +} + +export enum ConnectType { + ONLINE, + OFFLINE, +} diff --git a/src/plugins/EagProxyAAS/utils.ts b/src/plugins/EagProxyAAS/utils.ts index 1e9c959..01a88d9 100644 --- a/src/plugins/EagProxyAAS/utils.ts +++ b/src/plugins/EagProxyAAS/utils.ts @@ -1,4 +1,4 @@ -import { ServerGlobals } from "./types.js"; +import { ConnectType, ServerGlobals } from "./types.js"; import * as Chunk from "prismarine-chunk"; import * as Block from "prismarine-block"; import * as Registry from "prismarine-registry"; @@ -6,6 +6,7 @@ import vec3 from "vec3"; import { Client } from "minecraft-protocol"; import { ClientState, ConnectionState } from "./types.js"; import { auth, ServerDeviceCodeResponse } from "./auth.js"; +import { config } from "./config.js"; const { Vec3 } = vec3 as any; const Enums = PLUGIN_MANAGER.Enums; @@ -108,6 +109,33 @@ export function sendMessage(client: Client, msg: string) { }); } +export function sendCustomMessage( + client: Client, + msg: string, + color: string, + ...components: { text: string; color: string }[] +) { + client.write("chat", { + message: JSON.stringify( + components.length > 0 + ? { + text: msg, + color, + extra: components, + } + : { text: msg, color } + ), + position: 1, + }); +} + +export function sendChatComponent(client: Client, component: any) { + client.write("chat", { + message: JSON.stringify(component), + position: 1, + }); +} + export function sendMessageWarning(client: Client, msg: string) { client.write("chat", { message: JSON.stringify({ @@ -144,9 +172,7 @@ export function sendMessageLogin(client: Client, url: string, token: string) { color: "gold", hoverEvent: { action: "show_text", - value: - Enums.ChatColor.GOLD + - "Click me to copy to chat to copy from there!", + value: Enums.ChatColor.GOLD + "Click me to copy to chat!", }, clickEvent: { action: "suggest_command", @@ -164,11 +190,21 @@ export function sendMessageLogin(client: Client, url: string, token: string) { export function updateState( client: Client, - newState: "AUTH" | "SERVER", + newState: "CONNECTION_TYPE" | "AUTH" | "SERVER", uri?: string, code?: string ) { switch (newState) { + case "CONNECTION_TYPE": + client.write("playerlist_header", { + header: JSON.stringify({ + text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, + }), + footer: JSON.stringify({ + text: `${Enums.ChatColor.RED}Choose the connection type: 1 = online, 2 = offline.`, + }), + }); + break; case "AUTH": if (code == null || uri == null) throw new Error( @@ -189,7 +225,9 @@ export function updateState( text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, }), footer: JSON.stringify({ - text: `${Enums.ChatColor.RED}/join [port]`, + text: `${Enums.ChatColor.RED}/join ${ + config.allowCustomPorts ? " [port]" : "" + }`, }), }); break; @@ -206,108 +244,284 @@ export async function onConnect(client: ClientState) { `WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.` ); await new Promise((res) => setTimeout(res, 2000)); + sendMessageWarning( client.gameClient, `WARNING: It is highly suggested that you turn down settings, as gameplay tends to be very laggy and unplayable on low powered devices.` ); await new Promise((res) => setTimeout(res, 2000)); - sendMessageWarning( - client.gameClient, - `WARNING: You will be prompted to log in via Microsoft to obtain a session token necessary to join games. Any data related to your account will not be saved and for transparency reasons this proxy's source code is available on Github.` - ); - await new Promise((res) => setTimeout(res, 2000)); - client.lastStatusUpdate = Date.now(); - let errored = false, - savedAuth; - const authHandler = auth(), - codeCallback = (code: ServerDeviceCodeResponse) => { - updateState( - client.gameClient, - "AUTH", - code.verification_uri, - code.user_code - ); - sendMessageLogin( - client.gameClient, - code.verification_uri, - code.user_code - ); - }; - authHandler.once("error", (err) => { - if (!client.gameClient.ended) client.gameClient.end(err.message); - errored = true; + sendCustomMessage(client.gameClient, "What would you like to do?", "gray"); + sendChatComponent(client.gameClient, { + text: "1) ", + color: "gold", + extra: [ + { + text: "Connect to an online server (Minecraft account needed)", + color: "white", + }, + ], + hoverEvent: { + action: "show_text", + value: Enums.ChatColor.GOLD + "Click me to select!", + }, + clickEvent: { + action: "run_command", + value: "1", + }, }); - if (errored) return; - authHandler.on("code", codeCallback); - await new Promise((res) => - authHandler.once("done", (result) => { - savedAuth = result; - res(result); - }) - ); - sendMessage( + sendChatComponent(client.gameClient, { + text: "2) ", + color: "gold", + extra: [ + { + text: "Connect to an offline server (no Minecraft account needed)", + color: "white", + }, + ], + hoverEvent: { + action: "show_text", + value: Enums.ChatColor.GOLD + "Click me to select!", + }, + clickEvent: { + action: "run_command", + value: "2", + }, + }); + sendCustomMessage( client.gameClient, - Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!" + "Select an option from the above (1 = online, 2 = offline), either by clicking or manually typing out the option.", + "green" ); - client.state = ConnectionState.SUCCESS; - client.lastStatusUpdate = Date.now(); - updateState(client.gameClient, "SERVER"); - sendMessage( - client.gameClient, - `Provide a server to join. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` - ); - let host: string, port: number; + let chosenOption: ConnectType | null = null; while (true) { - const msg = await awaitCommand(client.gameClient, (msg) => - msg.startsWith("/join") - ), - parsed = msg.split(/ /gi, 3); - if (parsed.length < 2) - sendMessage( - client.gameClient, - `Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` - ); - else if (parsed.length > 3 && isNaN(parseInt(parsed[2]))) - sendMessage( - client.gameClient, - `A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join [port]${Enums.ChatColor.RESET}.` - ); - else { - host = parsed[1]; - if (parsed.length > 3) port = parseInt(parsed[2]); - port = port ?? 25565; - break; + const option = await awaitCommand(client.gameClient, (msg) => true); + switch (option) { + default: + sendCustomMessage( + client.gameClient, + `I don't understand what you meant by "${option}", please reply with a valid option!`, + "red" + ); + case "1": + chosenOption = ConnectType.ONLINE; + break; + case "2": + chosenOption = ConnectType.OFFLINE; + break; } + if (chosenOption != null) break; } - try { - await PLUGIN_MANAGER.proxy.players - .get(client.gameClient.username) - .switchServers({ - host: host, - port: port, - version: "1.8.8", - username: savedAuth.selectedProfile.name, - auth: "mojang", - keepAlive: false, - session: { - accessToken: savedAuth.accessToken, - clientToken: savedAuth.selectedProfile.id, - selectedProfile: { - id: savedAuth.selectedProfile.id, - name: savedAuth.selectedProfile.name, + + if (chosenOption == ConnectType.ONLINE) { + sendMessageWarning( + client.gameClient, + `You will be prompted to log in via Microsoft to obtain a session token necessary to join games. Any data related to your account will not be saved and for transparency reasons this proxy's source code is available on Github.` + ); + await new Promise((res) => setTimeout(res, 2000)); + + client.lastStatusUpdate = Date.now(); + let errored = false, + savedAuth; + const authHandler = auth(), + codeCallback = (code: ServerDeviceCodeResponse) => { + updateState( + client.gameClient, + "AUTH", + code.verification_uri, + code.user_code + ); + sendMessageLogin( + client.gameClient, + code.verification_uri, + code.user_code + ); + }; + authHandler.once("error", (err) => { + if (!client.gameClient.ended) client.gameClient.end(err.message); + errored = true; + }); + if (errored) return; + authHandler.on("code", codeCallback); + await new Promise((res) => + authHandler.once("done", (result) => { + savedAuth = result; + res(result); + }) + ); + sendMessage( + client.gameClient, + Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!" + ); + + client.state = ConnectionState.SUCCESS; + client.lastStatusUpdate = Date.now(); + updateState(client.gameClient, "SERVER"); + sendMessage( + client.gameClient, + `Provide a server to join. ${Enums.ChatColor.GOLD}/join ${ + config.allowCustomPorts ? " [port]" : "" + }${Enums.ChatColor.RESET}.` + ); + let host: string, port: number; + while (true) { + const msg = await awaitCommand(client.gameClient, (msg) => + msg.startsWith("/join") + ), + parsed = msg.split(/ /gi, 3); + if (parsed.length < 2) + sendMessage( + client.gameClient, + `Please provide a server to connect to. ${ + Enums.ChatColor.GOLD + }/join ${config.allowCustomPorts ? " [port]" : ""}${ + Enums.ChatColor.RESET + }.` + ); + else if (parsed.length > 2 && isNaN(parseInt(parsed[2]))) + sendMessage( + client.gameClient, + `A valid port number has to be passed! ${ + Enums.ChatColor.GOLD + }/join ${config.allowCustomPorts ? " [port]" : ""}${ + Enums.ChatColor.RESET + }.` + ); + else { + host = parsed[1]; + if (parsed.length > 2) port = parseInt(parsed[2]); + if (port != null && !config.allowCustomPorts) { + sendCustomMessage( + client.gameClient, + "You are not allowed to use custom server ports! /join ", + "red" + ); + host = null; + port = null; + } else { + port = port ?? 25565; + break; + } + } + } + try { + await PLUGIN_MANAGER.proxy.players + .get(client.gameClient.username) + .switchServers({ + host: host, + port: port, + version: "1.8.8", + username: savedAuth.selectedProfile.name, + auth: "mojang", + keepAlive: false, + session: { + accessToken: savedAuth.accessToken, + clientToken: savedAuth.selectedProfile.id, + selectedProfile: { + id: savedAuth.selectedProfile.id, + name: savedAuth.selectedProfile.name, + }, }, - }, - skipValidation: true, - hideErrors: true, - }); - } catch (err) { - if (!client.gameClient.ended) { - client.gameClient.end( - Enums.ChatColor.RED + - `Something went wrong whilst switching servers: ${err.message}` + skipValidation: true, + hideErrors: true, + }); + } catch (err) { + if (!client.gameClient.ended) { + client.gameClient.end( + Enums.ChatColor.RED + + `Something went wrong whilst switching servers: ${err.message}${ + err.code == "ENOTFOUND" + ? host.includes(":") + ? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.` + : "\nIs that IP valid?" + : "" + }` + ); + } + } + } else { + client.state = ConnectionState.SUCCESS; + client.lastStatusUpdate = Date.now(); + updateState(client.gameClient, "SERVER"); + sendMessage( + client.gameClient, + `Provide a server to join. ${Enums.ChatColor.GOLD}/join ${ + config.allowCustomPorts ? " [port]" : "" + }${Enums.ChatColor.RESET}.` + ); + let host: string, port: number; + while (true) { + const msg = await awaitCommand(client.gameClient, (msg) => + msg.startsWith("/join") + ), + parsed = msg.split(/ /gi, 3); + if (parsed.length < 2) + sendMessage( + client.gameClient, + `Please provide a server to connect to. ${ + Enums.ChatColor.GOLD + }/join ${config.allowCustomPorts ? " [port]" : ""}${ + Enums.ChatColor.RESET + }.` + ); + else if (parsed.length > 2 && isNaN(parseInt(parsed[2]))) + sendMessage( + client.gameClient, + `A valid port number has to be passed! ${ + Enums.ChatColor.GOLD + }/join ${config.allowCustomPorts ? " [port]" : ""}${ + Enums.ChatColor.RESET + }.` + ); + else { + host = parsed[1]; + if (parsed.length > 2) port = parseInt(parsed[2]); + if (port != null && !config.allowCustomPorts) { + sendCustomMessage( + client.gameClient, + "You are not allowed to use custom server ports! /join ", + "red" + ); + host = null; + port = null; + } else { + port = port ?? 25565; + break; + } + } + } + try { + sendCustomMessage( + client.gameClient, + "Attempting to switch servers, please wait... (if you don't get connected to the target server for a while, the server might be online only)", + "gray" ); + await PLUGIN_MANAGER.proxy.players + .get(client.gameClient.username) + .switchServers({ + host: host, + port: port, + version: "1.8.8", + username: client.gameClient.username, + auth: "offline", + keepAlive: false, + skipValidation: true, + hideErrors: true, + }); + } catch (err) { + if (!client.gameClient.ended) { + client.gameClient.end( + Enums.ChatColor.RED + + `Something went wrong whilst switching servers: ${err.message}${ + err.code == "ENOTFOUND" + ? host.includes(":") + ? `\n${Enums.ChatColor.GRAY}Suggestion: Replace the : in your IP with a space.` + : "\nIs that IP valid?" + : "" + }` + ); + } } } } catch (err) { diff --git a/src/proxy/Player.ts b/src/proxy/Player.ts index de02cdf..8f849d8 100644 --- a/src/proxy/Player.ts +++ b/src/proxy/Player.ts @@ -1,276 +1,370 @@ -import EventEmitter from "events" -import pkg, { Client, ClientOptions, createClient, states } from "minecraft-protocol" -import { WebSocket } from "ws" -import { Logger } from "../logger.js" -import { Chat } from "./Chat.js" -import { Constants } from "./Constants.js" -import { Enums } from "./Enums.js" -import Packet from "./Packet.js" -import SCDisconnectPacket from "./packets/SCDisconnectPacket.js" -import { MineProtocol } from "./Protocol.js" -import { EaglerSkins } from "./skins/EaglerSkins.js" -import { Util } from "./Util.js" -import { BungeeUtil } from "./BungeeUtil.js" +import EventEmitter from "events"; +import pkg, { + Client, + ClientOptions, + createClient, + states, +} from "minecraft-protocol"; +import { WebSocket } from "ws"; +import { Logger } from "../logger.js"; +import { Chat } from "./Chat.js"; +import { Constants } from "./Constants.js"; +import { Enums } from "./Enums.js"; +import Packet from "./Packet.js"; +import SCDisconnectPacket from "./packets/SCDisconnectPacket.js"; +import { MineProtocol } from "./Protocol.js"; +import { EaglerSkins } from "./skins/EaglerSkins.js"; +import { Util } from "./Util.js"; +import { BungeeUtil } from "./BungeeUtil.js"; -const { createSerializer, createDeserializer } = pkg +const { createSerializer, createDeserializer } = pkg; export class Player extends EventEmitter { - public ws: WebSocket - public username?: string - public skin?: EaglerSkins.EaglerSkin - public uuid?: string - public state?: Enums.ClientState = Enums.ClientState.PRE_HANDSHAKE - public serverConnection?: Client + public ws: WebSocket; + public username?: string; + public skin?: EaglerSkins.EaglerSkin; + public uuid?: string; + public state?: Enums.ClientState = Enums.ClientState.PRE_HANDSHAKE; + public serverConnection?: Client; - private _switchingServers: boolean = false - private _logger: Logger - private _alreadyConnected: boolean = false - private _serializer: any - private _deserializer: any - private _kickMessage: string - - constructor(ws: WebSocket, playerName?: string, serverConnection?: Client) { - super() - this._logger = new Logger(`PlayerHandler-${playerName}`) - this.ws = ws - this.username = playerName - this.serverConnection = serverConnection - if (this.username != null) this.uuid = Util.generateUUIDFromPlayer(this.username) - this._serializer = createSerializer({ - state: states.PLAY, - isServer: true, - version: "1.8.9", - customPackets: null - }) - this._deserializer = createDeserializer({ - state: states.PLAY, - isServer: false, - version: "1.8.9", - customPackets: null - }) - // this._serializer.pipe(this.ws) - } + private _switchingServers: boolean = false; + private _logger: Logger; + private _alreadyConnected: boolean = false; + private _serializer: any; + private _deserializer: any; + private _kickMessage: string; - public initListeners() { - this.ws.on('close', () => { - this.state = Enums.ClientState.DISCONNECTED - if (this.serverConnection) this.serverConnection.end() - this.emit('disconnect', this) - }) - this.ws.on('message', (msg: Buffer) => { - if (msg instanceof Buffer == false) return - const decoder = PACKET_REGISTRY.get(msg[0]) - if (decoder && decoder.sentAfterHandshake) { - if (!decoder && this.state != Enums.ClientState.POST_HANDSHAKE && msg.length >= 1) { - this._logger.warn(`Packet with ID 0x${Buffer.from([msg[0]]).toString('hex')} is missing a corresponding packet handler! Processing for this packet will be skipped.`) - } else { - let parsed: Packet, err: boolean - try { - parsed = new decoder.class() - parsed.deserialize(msg) - } catch (err) { - if (this.state != Enums.ClientState.POST_HANDSHAKE) this._logger.warn(`Packet ID 0x${Buffer.from([msg[0]]).toString('hex')} failed to parse! The packet will be skipped.`) - err = true - } - if (!err) { - this.emit('proxyPacket', parsed, this) - return - } - } - } - }) - } + constructor(ws: WebSocket, playerName?: string, serverConnection?: Client) { + super(); + this._logger = new Logger(`PlayerHandler-${playerName}`); + this.ws = ws; + this.username = playerName; + this.serverConnection = serverConnection; + if (this.username != null) + this.uuid = Util.generateUUIDFromPlayer(this.username); + this._serializer = createSerializer({ + state: states.PLAY, + isServer: true, + version: "1.8.9", + customPackets: null, + }); + this._deserializer = createDeserializer({ + state: states.PLAY, + isServer: false, + version: "1.8.9", + customPackets: null, + }); + // this._serializer.pipe(this.ws) + } - public write(packet: Packet) { - this.ws.send(packet.serialize()) - } - - public async read(packetId?: Enums.PacketId, filter?: (packet: Packet) => boolean): Promise { - let res - await Util.awaitPacket(this.ws, packet => { - if ((packetId != null && packetId == packet[0]) || (packetId == null)) { - const decoder = PACKET_REGISTRY.get(packet[0]) - if (decoder != null && decoder.packetId == packet[0] && (this.state == Enums.ClientState.PRE_HANDSHAKE || decoder.sentAfterHandshake) && decoder.boundTo == Enums.PacketBounds.S) { - let parsed: Packet, err = false - try { - parsed = new decoder.class() - parsed.deserialize(packet) - } catch (_err) { - err = true - } - if (!err) { - if (filter && filter(parsed)) { - res = parsed - return true - } else if (filter == null) { - res = parsed - return true - } - } - } - } - return false - }) - return res - } - - public disconnect(message: Chat.Chat | string) { - if (this.state == Enums.ClientState.POST_HANDSHAKE) { - this.ws.send(Buffer.concat([ - [0x40], - MineProtocol.writeString((typeof message == 'string' ? message : JSON.stringify(message))) - ].map(arr => arr instanceof Uint8Array ? arr : Buffer.from(arr)))) - this.ws.close() + public initListeners() { + this.ws.on("close", () => { + this.state = Enums.ClientState.DISCONNECTED; + if (this.serverConnection) this.serverConnection.end(); + this.emit("disconnect", this); + }); + this.ws.on("message", (msg: Buffer) => { + if (msg instanceof Buffer == false) return; + const decoder = PACKET_REGISTRY.get(msg[0]); + if (decoder && decoder.sentAfterHandshake) { + if ( + !decoder && + this.state != Enums.ClientState.POST_HANDSHAKE && + msg.length >= 1 + ) { + this._logger.warn( + `Packet with ID 0x${Buffer.from([msg[0]]).toString( + "hex" + )} is missing a corresponding packet handler! Processing for this packet will be skipped.` + ); } else { - const packet = new SCDisconnectPacket() - packet.reason = message - this.ws.send(packet.serialize()) - this.ws.close() + let parsed: Packet, err: boolean; + try { + parsed = new decoder.class(); + parsed.deserialize(msg); + } catch (err) { + if (this.state != Enums.ClientState.POST_HANDSHAKE) + this._logger.warn( + `Packet ID 0x${Buffer.from([msg[0]]).toString( + "hex" + )} failed to parse! The packet will be skipped.` + ); + err = true; + } + if (!err) { + this.emit("proxyPacket", parsed, this); + return; + } } - } + } + }); + } - public async connect(options: ClientOptions) { - if (this._alreadyConnected) - throw new Error(`Invalid state: Player has already been connected to a server, and .connect() was just called. Please use switchServers() instead.`) - this._alreadyConnected = true - this.serverConnection = createClient(Object.assign({ - version: '1.8.9', - keepAlive: false, - hideErrors: false - }, options)) - await this._bindListenersMineClient(this.serverConnection) - } + public write(packet: Packet) { + this.ws.send(packet.serialize()); + } - public switchServers(options: ClientOptions) { - if (!this._alreadyConnected) - throw new Error(`Invalid state: Player hasn't already been connected to a server, and .switchServers() has been called. Please use .connect() when initially connecting to a server, and only use .switchServers() if you want to switch servers.`) - return new Promise(async (res, rej) => { - const oldConnection = this.serverConnection - this._switchingServers = true - - this.ws.send(this._serializer.createPacketBuffer({ - name: 'chat', - params: { - message: `${Enums.ChatColor.GRAY}Switching servers...`, - position: 1 - } - })) - this.ws.send(this._serializer.createPacketBuffer({ - name: 'playerlist_header', - params: { - header: JSON.stringify({ - text: "" - }), - footer: JSON.stringify({ - text: "" - }) - } - })) - - this.serverConnection = createClient(Object.assign({ - version: '1.8.9', - keepAlive: false, - hideErrors: false - }, options)) - - await this._bindListenersMineClient(this.serverConnection, true, () => oldConnection.end()) - .then(() => { - this.emit('switchServer', this.serverConnection, this) - res() - }) - .catch(err => { - this.serverConnection = oldConnection - rej(err) - }) - }) - } - - private async _bindListenersMineClient(client: Client, switchingServers?: boolean, onSwitch?: Function) { - return new Promise((res, rej) => { - let stream = false, uuid - const listener = msg => { - if (stream) { - client.writeRaw(msg) - } - }, errListener = err => { - if (!stream) { - rej(err) - } else { - this.disconnect(`${Enums.ChatColor.RED}Something went wrong: ${err.stack ?? err}`) - } + public async read( + packetId?: Enums.PacketId, + filter?: (packet: Packet) => boolean + ): Promise { + let res; + await Util.awaitPacket(this.ws, (packet) => { + if ((packetId != null && packetId == packet[0]) || packetId == null) { + const decoder = PACKET_REGISTRY.get(packet[0]); + if ( + decoder != null && + decoder.packetId == packet[0] && + (this.state == Enums.ClientState.PRE_HANDSHAKE || + decoder.sentAfterHandshake) && + decoder.boundTo == Enums.PacketBounds.S + ) { + let parsed: Packet, + err = false; + try { + parsed = new decoder.class(); + parsed.deserialize(packet); + } catch (_err) { + err = true; + } + if (!err) { + if (filter && filter(parsed)) { + res = parsed; + return true; + } else if (filter == null) { + res = parsed; + return true; } - client.on('error', errListener) - client.on('end', reason => { - if (!this._switchingServers) this.disconnect(this._kickMessage ?? reason) - this.ws.removeListener('message', listener) - }) - client.once('connect', () => { - this.emit('joinServer', client, this) - }) - client.on('packet', (packet, meta) => { - if (meta.name == 'kick_disconnect') { - let json - try { json = JSON.parse(packet.reason) } - catch {} - if (json != null) { - this._kickMessage = Chat.chatToPlainString(json) - } else this._kickMessage = packet.reason - } - if (!stream) { - if (switchingServers) { - if (meta.name == 'login' && meta.state == states.PLAY && uuid) { - const pckSeq = BungeeUtil.getRespawnSequence(packet, this._serializer) - this.ws.send(this._serializer.createPacketBuffer({ - name: "login", - params: packet - })) - pckSeq.forEach(p => this.ws.send(p)) - stream = true - if (onSwitch) onSwitch() - res(null) - } else if (meta.name == 'success' && meta.state == states.LOGIN && !uuid) { - uuid = packet.uuid - } - } else { - if (meta.name == 'login' && meta.state == states.PLAY && uuid) { - this.ws.send(this._serializer.createPacketBuffer({ - name: "login", - params: packet - })) - stream = true - if (onSwitch) onSwitch() - res(null) - } else if (meta.name == 'success' && meta.state == states.LOGIN && !uuid) { - uuid = packet.uuid - } - } - } else { - this.ws.send(this._serializer.createPacketBuffer({ - name: meta.name, - params: packet - })) - } - }) - this.ws.on('message', listener) - }) + } + } + } + return false; + }); + return res; + } + + public disconnect(message: Chat.Chat | string) { + if (this.state == Enums.ClientState.POST_HANDSHAKE) { + this.ws.send( + Buffer.concat( + [ + [0x40], + MineProtocol.writeString( + typeof message == "string" ? message : JSON.stringify(message) + ), + ].map((arr) => (arr instanceof Uint8Array ? arr : Buffer.from(arr))) + ) + ); + this.ws.close(); + } else { + const packet = new SCDisconnectPacket(); + packet.reason = message; + this.ws.send(packet.serialize()); + this.ws.close(); } + } + + public async connect(options: ClientOptions) { + if (this._alreadyConnected) + throw new Error( + `Invalid state: Player has already been connected to a server, and .connect() was just called. Please use switchServers() instead.` + ); + this._alreadyConnected = true; + this.serverConnection = createClient( + Object.assign( + { + version: "1.8.9", + keepAlive: false, + hideErrors: false, + }, + options + ) + ); + await this._bindListenersMineClient(this.serverConnection); + } + + public switchServers(options: ClientOptions) { + if (!this._alreadyConnected) + throw new Error( + `Invalid state: Player hasn't already been connected to a server, and .switchServers() has been called. Please use .connect() when initially connecting to a server, and only use .switchServers() if you want to switch servers.` + ); + return new Promise(async (res, rej) => { + const oldConnection = this.serverConnection; + this._switchingServers = true; + + this.ws.send( + this._serializer.createPacketBuffer({ + name: "chat", + params: { + message: `${Enums.ChatColor.GRAY}Switching servers...`, + position: 1, + }, + }) + ); + this.ws.send( + this._serializer.createPacketBuffer({ + name: "playerlist_header", + params: { + header: JSON.stringify({ + text: "", + }), + footer: JSON.stringify({ + text: "", + }), + }, + }) + ); + + this.serverConnection = createClient( + Object.assign( + { + version: "1.8.9", + keepAlive: false, + hideErrors: false, + }, + options + ) + ); + + await this._bindListenersMineClient(this.serverConnection, true, () => + oldConnection.end() + ) + .then(() => { + this.emit("switchServer", this.serverConnection, this); + res(); + }) + .catch((err) => { + this.serverConnection = oldConnection; + rej(err); + }); + }); + } + + private async _bindListenersMineClient( + client: Client, + switchingServers?: boolean, + onSwitch?: Function + ) { + return new Promise((res, rej) => { + let stream = false, + uuid; + const listener = (msg) => { + if (stream) { + client.writeRaw(msg); + } + }, + errListener = (err) => { + if (!stream) { + rej(err); + } else { + this.disconnect( + `${Enums.ChatColor.RED}Something went wrong: ${err.stack ?? err}` + ); + } + }; + setTimeout(() => { + if (!stream) { + client.end("Timed out waiting for server connection."); + this.disconnect( + Enums.ChatColor.RED + "Timed out waiting for server connection!" + ); + throw new Error("Timed out waiting for server connection!"); + } + }, 30000); + client.on("error", errListener); + client.on("end", (reason) => { + if (!this._switchingServers) + this.disconnect(this._kickMessage ?? reason); + this.ws.removeListener("message", listener); + }); + client.once("connect", () => { + this.emit("joinServer", client, this); + }); + client.on("packet", (packet, meta) => { + if (meta.name == "kick_disconnect") { + let json; + try { + json = JSON.parse(packet.reason); + } catch {} + if (json != null) { + this._kickMessage = Chat.chatToPlainString(json); + } else this._kickMessage = packet.reason; + } + if (!stream) { + if (switchingServers) { + if (meta.name == "login" && meta.state == states.PLAY && uuid) { + const pckSeq = BungeeUtil.getRespawnSequence( + packet, + this._serializer + ); + this.ws.send( + this._serializer.createPacketBuffer({ + name: "login", + params: packet, + }) + ); + pckSeq.forEach((p) => this.ws.send(p)); + stream = true; + if (onSwitch) onSwitch(); + res(null); + } else if ( + meta.name == "success" && + meta.state == states.LOGIN && + !uuid + ) { + uuid = packet.uuid; + } + } else { + if (meta.name == "login" && meta.state == states.PLAY && uuid) { + this.ws.send( + this._serializer.createPacketBuffer({ + name: "login", + params: packet, + }) + ); + stream = true; + if (onSwitch) onSwitch(); + res(null); + } else if ( + meta.name == "success" && + meta.state == states.LOGIN && + !uuid + ) { + uuid = packet.uuid; + } + } + } else { + this.ws.send( + this._serializer.createPacketBuffer({ + name: meta.name, + params: packet, + }) + ); + } + }); + this.ws.on("message", listener); + }); + } } interface PlayerEvents { - 'switchServer': (connection: Client, player: Player) => void, - 'joinServer': (connection: Client, player: Player) => void, - // for vanilla game packets, bind to connection object instead - 'proxyPacket': (packet: Packet, player: Player) => void, - 'vanillaPacket': (packet: Packet & { cancel: boolean }, origin: 'CLIENT' | 'SERVER', player: Player) => Packet & { cancel: boolean }, - 'disconnect': (player: Player) => void + switchServer: (connection: Client, player: Player) => void; + joinServer: (connection: Client, player: Player) => void; + // for vanilla game packets, bind to connection object instead + proxyPacket: (packet: Packet, player: Player) => void; + vanillaPacket: ( + packet: Packet & { cancel: boolean }, + origin: "CLIENT" | "SERVER", + player: Player + ) => Packet & { cancel: boolean }; + disconnect: (player: Player) => void; } export declare interface Player { - on( - event: U, listener: PlayerEvents[U] - ): this; - - emit( - event: U, ...args: Parameters - ): boolean; -} \ No newline at end of file + on(event: U, listener: PlayerEvents[U]): this; + + emit( + event: U, + ...args: Parameters + ): boolean; +}