mirror of
https://github.com/WorldEditAxe/eaglerproxy.git
synced 2024-11-21 13:06:05 -08:00
add offline server support to EagProxyAAS
This commit is contained in:
parent
4915b182a8
commit
aaca19cedd
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user