mirror of
https://github.com/WorldEditAxe/eaglerproxy.git
synced 2024-11-08 06:56:03 -08:00
add offline server support to EagProxyAAS
This commit is contained in:
parent
4d1482a896
commit
e6baf8d60a
|
@ -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.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const config = {
|
||||
bindInternalServerPort: 25569,
|
||||
bindInternalServerIp: "127.0.0.1"
|
||||
}
|
||||
bindInternalServerPort: 25569,
|
||||
bindInternalServerIp: "127.0.0.1",
|
||||
allowCustomPorts: false,
|
||||
};
|
||||
|
|
|
@ -1,45 +1,50 @@
|
|||
import { Client, Server } from "minecraft-protocol"
|
||||
import { Client, Server } from "minecraft-protocol";
|
||||
|
||||
export type ServerGlobals = {
|
||||
server: Server,
|
||||
players: Map<string, ClientState>
|
||||
}
|
||||
server: Server;
|
||||
players: Map<string, ClientState>;
|
||||
};
|
||||
|
||||
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'
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
|
|
@ -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 <ip> [port]`,
|
||||
text: `${Enums.ChatColor.RED}/join <ip>${
|
||||
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 <ip> [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 <ip> [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 <ip> [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 <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 {
|
||||
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 <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) {
|
||||
|
|
|
@ -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<Packet> {
|
||||
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<void | never>(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<Packet> {
|
||||
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<void | never>(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<U extends keyof PlayerEvents>(
|
||||
event: U, listener: PlayerEvents[U]
|
||||
): this;
|
||||
|
||||
emit<U extends keyof PlayerEvents>(
|
||||
event: U, ...args: Parameters<PlayerEvents[U]>
|
||||
): boolean;
|
||||
}
|
||||
on<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
|
||||
|
||||
emit<U extends keyof PlayerEvents>(
|
||||
event: U,
|
||||
...args: Parameters<PlayerEvents[U]>
|
||||
): boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user