add offline server support to EagProxyAAS

This commit is contained in:
q13x 2023-07-03 00:24:45 -07:00
parent 4915b182a8
commit aaca19cedd
5 changed files with 705 additions and 392 deletions

View File

@ -48,7 +48,6 @@ EaglerProxy and EagProxyAAS:
EaglerProxy and EagProxyAAS does NOT: EaglerProxy and EagProxyAAS does NOT:
- include any Microsoft/Mojang code, - 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, - 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. - 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. - and intentionally put your account at risk.

View File

@ -1,4 +1,5 @@
export const config = { export const config = {
bindInternalServerPort: 25569, bindInternalServerPort: 25569,
bindInternalServerIp: "127.0.0.1" bindInternalServerIp: "127.0.0.1",
} allowCustomPorts: false,
};

View File

@ -1,21 +1,21 @@
import { Client, Server } from "minecraft-protocol" import { Client, Server } from "minecraft-protocol";
export type ServerGlobals = { export type ServerGlobals = {
server: Server, server: Server;
players: Map<string, ClientState> players: Map<string, ClientState>;
} };
export type ClientState = { export type ClientState = {
state: ConnectionState, state: ConnectionState;
gameClient: Client, gameClient: Client;
token?: string, token?: string;
lastStatusUpdate: number lastStatusUpdate: number;
} };
export enum ConnectionState { export enum ConnectionState {
AUTH, AUTH,
SUCCESS, SUCCESS,
DISCONNECTED DISCONNECTED,
} }
export enum ChatColor { export enum ChatColor {
@ -36,10 +36,15 @@ export enum ChatColor {
YELLOW = "§e", YELLOW = "§e",
WHITE = "§f", WHITE = "§f",
// text styling // text styling
OBFUSCATED = '§k', OBFUSCATED = "§k",
BOLD = '§l', BOLD = "§l",
STRIKETHROUGH = '§m', STRIKETHROUGH = "§m",
UNDERLINED = '§n', UNDERLINED = "§n",
ITALIC = '§o', ITALIC = "§o",
RESET = '§r' RESET = "§r",
}
export enum ConnectType {
ONLINE,
OFFLINE,
} }

View File

@ -1,4 +1,4 @@
import { ServerGlobals } from "./types.js"; import { ConnectType, ServerGlobals } from "./types.js";
import * as Chunk from "prismarine-chunk"; import * as Chunk from "prismarine-chunk";
import * as Block from "prismarine-block"; import * as Block from "prismarine-block";
import * as Registry from "prismarine-registry"; import * as Registry from "prismarine-registry";
@ -6,6 +6,7 @@ import vec3 from "vec3";
import { Client } from "minecraft-protocol"; import { Client } from "minecraft-protocol";
import { ClientState, ConnectionState } from "./types.js"; import { ClientState, ConnectionState } from "./types.js";
import { auth, ServerDeviceCodeResponse } from "./auth.js"; import { auth, ServerDeviceCodeResponse } from "./auth.js";
import { config } from "./config.js";
const { Vec3 } = vec3 as any; const { Vec3 } = vec3 as any;
const Enums = PLUGIN_MANAGER.Enums; 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) { export function sendMessageWarning(client: Client, msg: string) {
client.write("chat", { client.write("chat", {
message: JSON.stringify({ message: JSON.stringify({
@ -144,9 +172,7 @@ export function sendMessageLogin(client: Client, url: string, token: string) {
color: "gold", color: "gold",
hoverEvent: { hoverEvent: {
action: "show_text", action: "show_text",
value: value: Enums.ChatColor.GOLD + "Click me to copy to chat!",
Enums.ChatColor.GOLD +
"Click me to copy to chat to copy from there!",
}, },
clickEvent: { clickEvent: {
action: "suggest_command", action: "suggest_command",
@ -164,11 +190,21 @@ export function sendMessageLogin(client: Client, url: string, token: string) {
export function updateState( export function updateState(
client: Client, client: Client,
newState: "AUTH" | "SERVER", newState: "CONNECTION_TYPE" | "AUTH" | "SERVER",
uri?: string, uri?: string,
code?: string code?: string
) { ) {
switch (newState) { 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": case "AUTH":
if (code == null || uri == null) if (code == null || uri == null)
throw new Error( throw new Error(
@ -189,7 +225,9 @@ export function updateState(
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
}), }),
footer: JSON.stringify({ footer: JSON.stringify({
text: `${Enums.ChatColor.RED}/join <ip> [port]`, text: `${Enums.ChatColor.RED}/join <ip>${
config.allowCustomPorts ? " [port]" : ""
}`,
}), }),
}); });
break; break;
@ -206,14 +244,80 @@ 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.` `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)); await new Promise((res) => setTimeout(res, 2000));
sendMessageWarning( sendMessageWarning(
client.gameClient, 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.` `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)); await new Promise((res) => setTimeout(res, 2000));
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",
},
});
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,
"Select an option from the above (1 = online, 2 = offline), either by clicking or manually typing out the option.",
"green"
);
let chosenOption: ConnectType | null = null;
while (true) {
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;
}
if (chosenOption == ConnectType.ONLINE) {
sendMessageWarning( sendMessageWarning(
client.gameClient, 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.` `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)); await new Promise((res) => setTimeout(res, 2000));
@ -256,7 +360,9 @@ export async function onConnect(client: ClientState) {
updateState(client.gameClient, "SERVER"); updateState(client.gameClient, "SERVER");
sendMessage( sendMessage(
client.gameClient, client.gameClient,
`Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip> [port]${Enums.ChatColor.RESET}.` `Provide a server to join. ${Enums.ChatColor.GOLD}/join <ip>${
config.allowCustomPorts ? " [port]" : ""
}${Enums.ChatColor.RESET}.`
); );
let host: string, port: number; let host: string, port: number;
while (true) { while (true) {
@ -267,20 +373,38 @@ export async function onConnect(client: ClientState) {
if (parsed.length < 2) if (parsed.length < 2)
sendMessage( sendMessage(
client.gameClient, client.gameClient,
`Please provide a server to connect to. ${Enums.ChatColor.GOLD}/join <ip> [port]${Enums.ChatColor.RESET}.` `Please provide a server to connect to. ${
Enums.ChatColor.GOLD
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
Enums.ChatColor.RESET
}.`
); );
else if (parsed.length > 3 && isNaN(parseInt(parsed[2]))) else if (parsed.length > 2 && isNaN(parseInt(parsed[2])))
sendMessage( sendMessage(
client.gameClient, client.gameClient,
`A valid port number has to be passed! ${Enums.ChatColor.GOLD}/join <ip> [port]${Enums.ChatColor.RESET}.` `A valid port number has to be passed! ${
Enums.ChatColor.GOLD
}/join <ip>${config.allowCustomPorts ? " [port]" : ""}${
Enums.ChatColor.RESET
}.`
); );
else { else {
host = parsed[1]; host = parsed[1];
if (parsed.length > 3) port = parseInt(parsed[2]); 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 <ip>",
"red"
);
host = null;
port = null;
} else {
port = port ?? 25565; port = port ?? 25565;
break; break;
} }
} }
}
try { try {
await PLUGIN_MANAGER.proxy.players await PLUGIN_MANAGER.proxy.players
.get(client.gameClient.username) .get(client.gameClient.username)
@ -306,10 +430,100 @@ export async function onConnect(client: ClientState) {
if (!client.gameClient.ended) { if (!client.gameClient.ended) {
client.gameClient.end( client.gameClient.end(
Enums.ChatColor.RED + Enums.ChatColor.RED +
`Something went wrong whilst switching servers: ${err.message}` `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 <ip>${
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 <ip>${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 <ip>${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 <ip>",
"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) { } catch (err) {
if (!client.gameClient.ended) { if (!client.gameClient.ended) {
logger.error( logger.error(

View File

@ -1,276 +1,370 @@
import EventEmitter from "events" import EventEmitter from "events";
import pkg, { Client, ClientOptions, createClient, states } from "minecraft-protocol" import pkg, {
import { WebSocket } from "ws" Client,
import { Logger } from "../logger.js" ClientOptions,
import { Chat } from "./Chat.js" createClient,
import { Constants } from "./Constants.js" states,
import { Enums } from "./Enums.js" } from "minecraft-protocol";
import Packet from "./Packet.js" import { WebSocket } from "ws";
import SCDisconnectPacket from "./packets/SCDisconnectPacket.js" import { Logger } from "../logger.js";
import { MineProtocol } from "./Protocol.js" import { Chat } from "./Chat.js";
import { EaglerSkins } from "./skins/EaglerSkins.js" import { Constants } from "./Constants.js";
import { Util } from "./Util.js" import { Enums } from "./Enums.js";
import { BungeeUtil } from "./BungeeUtil.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 { export class Player extends EventEmitter {
public ws: WebSocket public ws: WebSocket;
public username?: string public username?: string;
public skin?: EaglerSkins.EaglerSkin public skin?: EaglerSkins.EaglerSkin;
public uuid?: string public uuid?: string;
public state?: Enums.ClientState = Enums.ClientState.PRE_HANDSHAKE public state?: Enums.ClientState = Enums.ClientState.PRE_HANDSHAKE;
public serverConnection?: Client public serverConnection?: Client;
private _switchingServers: boolean = false private _switchingServers: boolean = false;
private _logger: Logger private _logger: Logger;
private _alreadyConnected: boolean = false private _alreadyConnected: boolean = false;
private _serializer: any private _serializer: any;
private _deserializer: any private _deserializer: any;
private _kickMessage: string private _kickMessage: string;
constructor(ws: WebSocket, playerName?: string, serverConnection?: Client) { constructor(ws: WebSocket, playerName?: string, serverConnection?: Client) {
super() super();
this._logger = new Logger(`PlayerHandler-${playerName}`) this._logger = new Logger(`PlayerHandler-${playerName}`);
this.ws = ws this.ws = ws;
this.username = playerName this.username = playerName;
this.serverConnection = serverConnection this.serverConnection = serverConnection;
if (this.username != null) this.uuid = Util.generateUUIDFromPlayer(this.username) if (this.username != null)
this.uuid = Util.generateUUIDFromPlayer(this.username);
this._serializer = createSerializer({ this._serializer = createSerializer({
state: states.PLAY, state: states.PLAY,
isServer: true, isServer: true,
version: "1.8.9", version: "1.8.9",
customPackets: null customPackets: null,
}) });
this._deserializer = createDeserializer({ this._deserializer = createDeserializer({
state: states.PLAY, state: states.PLAY,
isServer: false, isServer: false,
version: "1.8.9", version: "1.8.9",
customPackets: null customPackets: null,
}) });
// this._serializer.pipe(this.ws) // this._serializer.pipe(this.ws)
} }
public initListeners() { public initListeners() {
this.ws.on('close', () => { this.ws.on("close", () => {
this.state = Enums.ClientState.DISCONNECTED this.state = Enums.ClientState.DISCONNECTED;
if (this.serverConnection) this.serverConnection.end() if (this.serverConnection) this.serverConnection.end();
this.emit('disconnect', this) this.emit("disconnect", this);
}) });
this.ws.on('message', (msg: Buffer) => { this.ws.on("message", (msg: Buffer) => {
if (msg instanceof Buffer == false) return if (msg instanceof Buffer == false) return;
const decoder = PACKET_REGISTRY.get(msg[0]) const decoder = PACKET_REGISTRY.get(msg[0]);
if (decoder && decoder.sentAfterHandshake) { if (decoder && decoder.sentAfterHandshake) {
if (!decoder && this.state != Enums.ClientState.POST_HANDSHAKE && msg.length >= 1) { if (
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.`) !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 { } else {
let parsed: Packet, err: boolean let parsed: Packet, err: boolean;
try { try {
parsed = new decoder.class() parsed = new decoder.class();
parsed.deserialize(msg) parsed.deserialize(msg);
} catch (err) { } 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.`) if (this.state != Enums.ClientState.POST_HANDSHAKE)
err = true this._logger.warn(
`Packet ID 0x${Buffer.from([msg[0]]).toString(
"hex"
)} failed to parse! The packet will be skipped.`
);
err = true;
} }
if (!err) { if (!err) {
this.emit('proxyPacket', parsed, this) this.emit("proxyPacket", parsed, this);
return return;
} }
} }
} }
}) });
} }
public write(packet: Packet) { public write(packet: Packet) {
this.ws.send(packet.serialize()) this.ws.send(packet.serialize());
} }
public async read(packetId?: Enums.PacketId, filter?: (packet: Packet) => boolean): Promise<Packet> { public async read(
let res packetId?: Enums.PacketId,
await Util.awaitPacket(this.ws, packet => { filter?: (packet: Packet) => boolean
if ((packetId != null && packetId == packet[0]) || (packetId == null)) { ): Promise<Packet> {
const decoder = PACKET_REGISTRY.get(packet[0]) let res;
if (decoder != null && decoder.packetId == packet[0] && (this.state == Enums.ClientState.PRE_HANDSHAKE || decoder.sentAfterHandshake) && decoder.boundTo == Enums.PacketBounds.S) { await Util.awaitPacket(this.ws, (packet) => {
let parsed: Packet, err = false 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 { try {
parsed = new decoder.class() parsed = new decoder.class();
parsed.deserialize(packet) parsed.deserialize(packet);
} catch (_err) { } catch (_err) {
err = true err = true;
} }
if (!err) { if (!err) {
if (filter && filter(parsed)) { if (filter && filter(parsed)) {
res = parsed res = parsed;
return true return true;
} else if (filter == null) { } else if (filter == null) {
res = parsed res = parsed;
return true return true;
} }
} }
} }
} }
return false return false;
}) });
return res return res;
} }
public disconnect(message: Chat.Chat | string) { public disconnect(message: Chat.Chat | string) {
if (this.state == Enums.ClientState.POST_HANDSHAKE) { if (this.state == Enums.ClientState.POST_HANDSHAKE) {
this.ws.send(Buffer.concat([ this.ws.send(
Buffer.concat(
[
[0x40], [0x40],
MineProtocol.writeString((typeof message == 'string' ? message : JSON.stringify(message))) MineProtocol.writeString(
].map(arr => arr instanceof Uint8Array ? arr : Buffer.from(arr)))) typeof message == "string" ? message : JSON.stringify(message)
this.ws.close() ),
].map((arr) => (arr instanceof Uint8Array ? arr : Buffer.from(arr)))
)
);
this.ws.close();
} else { } else {
const packet = new SCDisconnectPacket() const packet = new SCDisconnectPacket();
packet.reason = message packet.reason = message;
this.ws.send(packet.serialize()) this.ws.send(packet.serialize());
this.ws.close() this.ws.close();
} }
} }
public async connect(options: ClientOptions) { public async connect(options: ClientOptions) {
if (this._alreadyConnected) 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.`) throw new Error(
this._alreadyConnected = true `Invalid state: Player has already been connected to a server, and .connect() was just called. Please use switchServers() instead.`
this.serverConnection = createClient(Object.assign({ );
version: '1.8.9', this._alreadyConnected = true;
this.serverConnection = createClient(
Object.assign(
{
version: "1.8.9",
keepAlive: false, keepAlive: false,
hideErrors: false hideErrors: false,
}, options)) },
await this._bindListenersMineClient(this.serverConnection) options
)
);
await this._bindListenersMineClient(this.serverConnection);
} }
public switchServers(options: ClientOptions) { public switchServers(options: ClientOptions) {
if (!this._alreadyConnected) 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.`) 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<void | never>(async (res, rej) => { return new Promise<void | never>(async (res, rej) => {
const oldConnection = this.serverConnection const oldConnection = this.serverConnection;
this._switchingServers = true this._switchingServers = true;
this.ws.send(this._serializer.createPacketBuffer({ this.ws.send(
name: 'chat', this._serializer.createPacketBuffer({
name: "chat",
params: { params: {
message: `${Enums.ChatColor.GRAY}Switching servers...`, message: `${Enums.ChatColor.GRAY}Switching servers...`,
position: 1 position: 1,
} },
})) })
this.ws.send(this._serializer.createPacketBuffer({ );
name: 'playerlist_header', this.ws.send(
this._serializer.createPacketBuffer({
name: "playerlist_header",
params: { params: {
header: JSON.stringify({ header: JSON.stringify({
text: "" text: "",
}), }),
footer: JSON.stringify({ footer: JSON.stringify({
text: "" text: "",
}),
},
}) })
} );
}))
this.serverConnection = createClient(Object.assign({ this.serverConnection = createClient(
version: '1.8.9', Object.assign(
{
version: "1.8.9",
keepAlive: false, keepAlive: false,
hideErrors: false hideErrors: false,
}, options)) },
options
)
);
await this._bindListenersMineClient(this.serverConnection, true, () => oldConnection.end()) await this._bindListenersMineClient(this.serverConnection, true, () =>
oldConnection.end()
)
.then(() => { .then(() => {
this.emit('switchServer', this.serverConnection, this) this.emit("switchServer", this.serverConnection, this);
res() res();
})
.catch(err => {
this.serverConnection = oldConnection
rej(err)
})
}) })
.catch((err) => {
this.serverConnection = oldConnection;
rej(err);
});
});
} }
private async _bindListenersMineClient(client: Client, switchingServers?: boolean, onSwitch?: Function) { private async _bindListenersMineClient(
client: Client,
switchingServers?: boolean,
onSwitch?: Function
) {
return new Promise((res, rej) => { return new Promise((res, rej) => {
let stream = false, uuid let stream = false,
const listener = msg => { uuid;
const listener = (msg) => {
if (stream) { if (stream) {
client.writeRaw(msg) client.writeRaw(msg);
} }
}, errListener = err => { },
errListener = (err) => {
if (!stream) { if (!stream) {
rej(err) rej(err);
} else { } else {
this.disconnect(`${Enums.ChatColor.RED}Something went wrong: ${err.stack ?? err}`) 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!");
} }
client.on('error', errListener) }, 30000);
client.on('end', reason => { client.on("error", errListener);
if (!this._switchingServers) this.disconnect(this._kickMessage ?? reason) client.on("end", (reason) => {
this.ws.removeListener('message', listener) if (!this._switchingServers)
}) this.disconnect(this._kickMessage ?? reason);
client.once('connect', () => { this.ws.removeListener("message", listener);
this.emit('joinServer', client, this) });
}) client.once("connect", () => {
client.on('packet', (packet, meta) => { this.emit("joinServer", client, this);
if (meta.name == 'kick_disconnect') { });
let json client.on("packet", (packet, meta) => {
try { json = JSON.parse(packet.reason) } if (meta.name == "kick_disconnect") {
catch {} let json;
try {
json = JSON.parse(packet.reason);
} catch {}
if (json != null) { if (json != null) {
this._kickMessage = Chat.chatToPlainString(json) this._kickMessage = Chat.chatToPlainString(json);
} else this._kickMessage = packet.reason } else this._kickMessage = packet.reason;
} }
if (!stream) { if (!stream) {
if (switchingServers) { if (switchingServers) {
if (meta.name == 'login' && meta.state == states.PLAY && uuid) { if (meta.name == "login" && meta.state == states.PLAY && uuid) {
const pckSeq = BungeeUtil.getRespawnSequence(packet, this._serializer) const pckSeq = BungeeUtil.getRespawnSequence(
this.ws.send(this._serializer.createPacketBuffer({ packet,
this._serializer
);
this.ws.send(
this._serializer.createPacketBuffer({
name: "login", name: "login",
params: packet params: packet,
})) })
pckSeq.forEach(p => this.ws.send(p)) );
stream = true pckSeq.forEach((p) => this.ws.send(p));
if (onSwitch) onSwitch() stream = true;
res(null) if (onSwitch) onSwitch();
} else if (meta.name == 'success' && meta.state == states.LOGIN && !uuid) { res(null);
uuid = packet.uuid } else if (
meta.name == "success" &&
meta.state == states.LOGIN &&
!uuid
) {
uuid = packet.uuid;
} }
} else { } else {
if (meta.name == 'login' && meta.state == states.PLAY && uuid) { if (meta.name == "login" && meta.state == states.PLAY && uuid) {
this.ws.send(this._serializer.createPacketBuffer({ this.ws.send(
this._serializer.createPacketBuffer({
name: "login", name: "login",
params: packet params: packet,
})) })
stream = true );
if (onSwitch) onSwitch() stream = true;
res(null) if (onSwitch) onSwitch();
} else if (meta.name == 'success' && meta.state == states.LOGIN && !uuid) { res(null);
uuid = packet.uuid } else if (
meta.name == "success" &&
meta.state == states.LOGIN &&
!uuid
) {
uuid = packet.uuid;
} }
} }
} else { } else {
this.ws.send(this._serializer.createPacketBuffer({ this.ws.send(
this._serializer.createPacketBuffer({
name: meta.name, name: meta.name,
params: packet params: packet,
})) })
);
} }
}) });
this.ws.on('message', listener) this.ws.on("message", listener);
}) });
} }
} }
interface PlayerEvents { interface PlayerEvents {
'switchServer': (connection: Client, player: Player) => void, switchServer: (connection: Client, player: Player) => void;
'joinServer': (connection: Client, player: Player) => void, joinServer: (connection: Client, player: Player) => void;
// for vanilla game packets, bind to connection object instead // for vanilla game packets, bind to connection object instead
'proxyPacket': (packet: Packet, player: Player) => void, proxyPacket: (packet: Packet, player: Player) => void;
'vanillaPacket': (packet: Packet & { cancel: boolean }, origin: 'CLIENT' | 'SERVER', player: Player) => Packet & { cancel: boolean }, vanillaPacket: (
'disconnect': (player: Player) => void packet: Packet & { cancel: boolean },
origin: "CLIENT" | "SERVER",
player: Player
) => Packet & { cancel: boolean };
disconnect: (player: Player) => void;
} }
export declare interface Player { export declare interface Player {
on<U extends keyof PlayerEvents>( on<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
event: U, listener: PlayerEvents[U]
): this;
emit<U extends keyof PlayerEvents>( emit<U extends keyof PlayerEvents>(
event: U, ...args: Parameters<PlayerEvents[U]> event: U,
...args: Parameters<PlayerEvents[U]>
): boolean; ): boolean;
} }