mirror of
https://github.com/WorldEditAxe/eaglerproxy.git
synced 2024-11-21 04:56:04 -08:00
Add EagProxyAAS password protection + direct connect
This commit is contained in:
parent
61cbcfca3e
commit
67c3e0bbda
1958
package-lock.json
generated
1958
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -2,4 +2,9 @@ export const config = {
|
|||
bindInternalServerPort: 25569,
|
||||
bindInternalServerIp: "127.0.0.1",
|
||||
allowCustomPorts: true,
|
||||
disallowHypixel: false,
|
||||
authentication: {
|
||||
enabled: true,
|
||||
password: "nope",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
import { config } from "./config.js";
|
||||
import { createServer } from "minecraft-protocol";
|
||||
import { ClientState, ConnectionState, ServerGlobals } from "./types.js";
|
||||
import { handleConnect, hushConsole, setSG } from "./utils.js";
|
||||
import { handleConnect, hushConsole, sendChatComponent, setSG } from "./utils.js";
|
||||
import path from "path";
|
||||
import { readFileSync } from "fs";
|
||||
import { handleCommand } from "./commands.js";
|
||||
import { registerEndpoints } from "./service/endpoints.js";
|
||||
|
||||
const PluginManager = PLUGIN_MANAGER;
|
||||
const metadata = JSON.parse(
|
||||
readFileSync(
|
||||
process.platform == "win32"
|
||||
? path
|
||||
.join(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
"metadata.json"
|
||||
)
|
||||
.slice(1)
|
||||
: path.join(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
"metadata.json"
|
||||
)
|
||||
).toString()
|
||||
readFileSync(process.platform == "win32" ? path.join(path.dirname(new URL(import.meta.url).pathname), "metadata.json").slice(1) : path.join(path.dirname(new URL(import.meta.url).pathname), "metadata.json")).toString()
|
||||
);
|
||||
|
||||
const Logger = PluginManager.Logger;
|
||||
|
@ -31,14 +21,11 @@ const Player = PluginManager.Player;
|
|||
const MineProtocol = PluginManager.MineProtocol;
|
||||
const EaglerSkins = PluginManager.EaglerSkins;
|
||||
const Util = PluginManager.Util;
|
||||
|
||||
hushConsole();
|
||||
|
||||
const logger = new Logger("EaglerProxyAAS");
|
||||
logger.info(`Starting ${metadata.name} v${metadata.version}...`);
|
||||
logger.info(
|
||||
`(internal server port: ${config.bindInternalServerPort}, internal server IP: ${config.bindInternalServerPort})`
|
||||
);
|
||||
logger.info(`(internal server port: ${config.bindInternalServerPort}, internal server IP: ${config.bindInternalServerPort})`);
|
||||
|
||||
logger.info("Starting internal server...");
|
||||
let server = createServer({
|
||||
|
@ -55,28 +42,178 @@ let server = createServer({
|
|||
setSG(sGlobals);
|
||||
|
||||
server.on("login", (client) => {
|
||||
logger.info(
|
||||
`Client ${client.username} has connected to the authentication server.`
|
||||
);
|
||||
client.on("end", () => {
|
||||
sGlobals.players.delete(client.username);
|
||||
logger.info(
|
||||
`Client ${client.username} has disconnected from the authentication server.`
|
||||
);
|
||||
});
|
||||
const cs: ClientState = {
|
||||
state: ConnectionState.AUTH,
|
||||
gameClient: client,
|
||||
token: null,
|
||||
lastStatusUpdate: null,
|
||||
};
|
||||
sGlobals.players.set(client.username, cs);
|
||||
handleConnect(cs);
|
||||
const proxyPlayer = PluginManager.proxy.players.get(client.username);
|
||||
if (proxyPlayer != null) {
|
||||
const url = new URL(proxyPlayer.ws.httpRequest.url, `http${PluginManager.proxy.config.tls?.enabled ? "s" : ""}://${proxyPlayer.ws.httpRequest.headers.host}`);
|
||||
if (url.pathname == "/connect-vanilla") {
|
||||
const host = url.searchParams.get("ip"),
|
||||
port = url.searchParams.get("port"),
|
||||
type: "OFFLINE" | "ONLINE" = url.searchParams.get("authType") as any;
|
||||
|
||||
if (isNaN(Number(port))) return proxyPlayer.disconnect(Enums.ChatColor.RED + "Bad port number");
|
||||
if (
|
||||
!/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/.test(
|
||||
host
|
||||
)
|
||||
) {
|
||||
return proxyPlayer.disconnect(Enums.ChatColor.RED + "Bad host provided");
|
||||
}
|
||||
|
||||
if (type == "ONLINE") {
|
||||
const _profile = proxyPlayer.ws.httpRequest.headers["Minecraft-Profile"];
|
||||
if (!_profile) proxyPlayer.disconnect(Enums.ChatColor.RED + "Missing Minecraft-Profile header");
|
||||
let profile;
|
||||
try {
|
||||
profile = JSON.parse(_profile as string);
|
||||
} catch (err) {
|
||||
proxyPlayer.disconnect(Enums.ChatColor.RED + "Could not read Minecraft-Profile header");
|
||||
}
|
||||
|
||||
logger.info(`Direct OFFLINE proxy forward connection from Eaglercraft player (${client.username}) received.`);
|
||||
proxyPlayer.on("vanillaPacket", (packet, origin) => {
|
||||
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||
packet.cancel = true;
|
||||
handleCommand(proxyPlayer, packet.params.message as string);
|
||||
}
|
||||
});
|
||||
sendChatComponent(client, {
|
||||
text: `Joining server under ${profile.selectedProfile.name}/your Minecraft account's username! Run `,
|
||||
color: "aqua",
|
||||
extra: [
|
||||
{
|
||||
text: "/eag-help",
|
||||
color: "gold",
|
||||
hoverEvent: {
|
||||
action: "show_text",
|
||||
value: Enums.ChatColor.GOLD + "Click me to run this command!",
|
||||
},
|
||||
clickEvent: {
|
||||
action: "run_command",
|
||||
value: "/eag-help",
|
||||
},
|
||||
},
|
||||
{
|
||||
text: " for a list of proxy commands.",
|
||||
color: "aqua",
|
||||
},
|
||||
],
|
||||
});
|
||||
(proxyPlayer as any)._onlineSession = {
|
||||
auth: "mojang",
|
||||
username: profile.selectedProfile.name,
|
||||
session: {
|
||||
accessToken: profile.accessToken,
|
||||
clientToken: profile.selectedProfile.id,
|
||||
selectedProfile: {
|
||||
id: profile.selectedProfile.id,
|
||||
name: profile.selectedProfile.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
proxyPlayer
|
||||
.switchServers({
|
||||
host: host,
|
||||
port: Number(port),
|
||||
version: "1.8.8",
|
||||
username: profile.selectedProfile.name,
|
||||
auth: "mojang",
|
||||
keepAlive: false,
|
||||
session: {
|
||||
accessToken: profile.accessToken,
|
||||
clientToken: profile.selectedProfile.id,
|
||||
selectedProfile: {
|
||||
id: profile.selectedProfile.id,
|
||||
name: profile.selectedProfile.name,
|
||||
},
|
||||
},
|
||||
skipValidation: true,
|
||||
hideErrors: true,
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!client.ended) {
|
||||
proxyPlayer.disconnect(
|
||||
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 if (type == "OFFLINE") {
|
||||
logger.info(`Direct ONLINE proxy forward connection from Eaglercraft player (${client.username}) received.`);
|
||||
logger.info(`Player ${client.username} is attempting to connect to ${host}:${port} under their Eaglercraft username (${client.username}) using offline mode!`);
|
||||
proxyPlayer.on("vanillaPacket", (packet, origin) => {
|
||||
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||
packet.cancel = true;
|
||||
handleCommand(proxyPlayer, packet.params.message as string);
|
||||
}
|
||||
});
|
||||
|
||||
sendChatComponent(client, {
|
||||
text: `Joining server under ${client.username}/your Eaglercraft account's username! Run `,
|
||||
color: "aqua",
|
||||
extra: [
|
||||
{
|
||||
text: "/eag-help",
|
||||
color: "gold",
|
||||
hoverEvent: {
|
||||
action: "show_text",
|
||||
value: Enums.ChatColor.GOLD + "Click me to run this command!",
|
||||
},
|
||||
clickEvent: {
|
||||
action: "run_command",
|
||||
value: "/eag-help",
|
||||
},
|
||||
},
|
||||
{
|
||||
text: " for a list of proxy commands.",
|
||||
color: "aqua",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
proxyPlayer
|
||||
.switchServers({
|
||||
host: host,
|
||||
port: Number(port),
|
||||
auth: "offline",
|
||||
username: client.username,
|
||||
version: "1.8.8",
|
||||
keepAlive: false,
|
||||
skipValidation: true,
|
||||
hideErrors: true,
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!client.ended) {
|
||||
proxyPlayer.disconnect(
|
||||
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 {
|
||||
proxyPlayer.disconnect(Enums.ChatColor.RED + "Missing authentication type");
|
||||
}
|
||||
} else {
|
||||
logger.info(`Client ${client.username} has connected to the authentication server.`);
|
||||
client.on("end", () => {
|
||||
sGlobals.players.delete(client.username);
|
||||
logger.info(`Client ${client.username} has disconnected from the authentication server.`);
|
||||
});
|
||||
const cs: ClientState = {
|
||||
state: ConnectionState.AUTH,
|
||||
gameClient: client,
|
||||
token: null,
|
||||
lastStatusUpdate: null,
|
||||
};
|
||||
sGlobals.players.set(client.username, cs);
|
||||
handleConnect(cs);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Proxy player object is null for ${client.username}?!`);
|
||||
client.end("Indirect connection to internal authentication server detected!");
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(
|
||||
"Redirecting backend server IP... (this is required for the plugin to function)"
|
||||
);
|
||||
logger.info("Redirecting backend server IP... (this is required for the plugin to function)");
|
||||
CONFIG.adapter.server = {
|
||||
host: config.bindInternalServerIp,
|
||||
port: config.bindInternalServerPort,
|
||||
|
@ -84,3 +221,7 @@ CONFIG.adapter.server = {
|
|||
CONFIG.adapter.motd = {
|
||||
l1: Enums.ChatColor.GOLD + "EaglerProxy as a Service",
|
||||
};
|
||||
|
||||
PLUGIN_MANAGER.addListener("proxyFinishLoading", () => {
|
||||
registerEndpoints();
|
||||
});
|
||||
|
|
33
src/plugins/EagProxyAAS/service/endpoints.ts
Normal file
33
src/plugins/EagProxyAAS/service/endpoints.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { config } from "../config.js";
|
||||
|
||||
export async function registerEndpoints() {
|
||||
const proxy = PLUGIN_MANAGER.proxy;
|
||||
proxy.on("httpConnection", (req, res, ctx) => {
|
||||
if (req.url.startsWith("/eagpaas/metadata")) {
|
||||
ctx.handled = true;
|
||||
res.writeHead(200).end(
|
||||
JSON.stringify({
|
||||
branding: "EagProxyAAS",
|
||||
version: "1",
|
||||
})
|
||||
);
|
||||
} else if (req.url.startsWith("/eagpaas/validate")) {
|
||||
ctx.handled = true;
|
||||
if (config.authentication.enabled) {
|
||||
if (req.headers["authorization"] !== `Basic ${config.authentication.password}`) {
|
||||
return res.writeHead(403).end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
reason: "Access Denied",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
res.writeHead(200).end(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -37,21 +37,10 @@ export function setSG(svr: ServerGlobals) {
|
|||
|
||||
export function disconectIdle() {
|
||||
SERVER.players.forEach((client) => {
|
||||
if (
|
||||
client.state == ConnectionState.AUTH &&
|
||||
Date.now() - client.lastStatusUpdate > MAX_LIFETIME_AUTH
|
||||
) {
|
||||
client.gameClient.end(
|
||||
"Timed out waiting for user to login via Microsoft"
|
||||
);
|
||||
} else if (
|
||||
client.state == ConnectionState.SUCCESS &&
|
||||
Date.now() - client.lastStatusUpdate > MAX_LIFETIME_CONNECTED
|
||||
) {
|
||||
client.gameClient.end(
|
||||
Enums.ChatColor.RED +
|
||||
"Please enter the IP of the server you'd like to connect to in chat."
|
||||
);
|
||||
if (client.state == ConnectionState.AUTH && Date.now() - client.lastStatusUpdate > MAX_LIFETIME_AUTH) {
|
||||
client.gameClient.end("Timed out waiting for user to login via Microsoft");
|
||||
} else if (client.state == ConnectionState.SUCCESS && Date.now() - client.lastStatusUpdate > MAX_LIFETIME_CONNECTED) {
|
||||
client.gameClient.end(Enums.ChatColor.RED + "Please enter the IP of the server you'd like to connect to in chat.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -93,10 +82,7 @@ export function handleConnect(client: ClientState) {
|
|||
onConnect(client);
|
||||
}
|
||||
|
||||
export function awaitCommand(
|
||||
client: Client,
|
||||
filter: (msg: string) => boolean
|
||||
): Promise<string> {
|
||||
export function awaitCommand(client: Client, filter: (msg: string) => boolean): Promise<string> {
|
||||
return new Promise<string>((res, rej) => {
|
||||
const onMsg = (packet) => {
|
||||
if (filter(packet.message)) {
|
||||
|
@ -105,8 +91,7 @@ export function awaitCommand(
|
|||
res(packet.message);
|
||||
}
|
||||
};
|
||||
const onEnd = () =>
|
||||
rej("Client disconnected before promise could be resolved");
|
||||
const onEnd = () => rej("Client disconnected before promise could be resolved");
|
||||
client.on("chat", onMsg);
|
||||
client.on("end", onEnd);
|
||||
});
|
||||
|
@ -119,12 +104,7 @@ export function sendMessage(client: Client, msg: string) {
|
|||
});
|
||||
}
|
||||
|
||||
export function sendCustomMessage(
|
||||
client: Client,
|
||||
msg: string,
|
||||
color: string,
|
||||
...components: { text: string; color: string }[]
|
||||
) {
|
||||
export function sendCustomMessage(client: Client, msg: string, color: string, ...components: { text: string; color: string }[]) {
|
||||
client.write("chat", {
|
||||
message: JSON.stringify(
|
||||
components.length > 0
|
||||
|
@ -198,12 +178,7 @@ export function sendMessageLogin(client: Client, url: string, token: string) {
|
|||
});
|
||||
}
|
||||
|
||||
export function updateState(
|
||||
client: Client,
|
||||
newState: "CONNECTION_TYPE" | "AUTH_EASYMC" | "AUTH" | "SERVER",
|
||||
uri?: string,
|
||||
code?: string
|
||||
) {
|
||||
export function updateState(client: Client, newState: "CONNECTION_TYPE" | "AUTH_EASYMC" | "AUTH" | "SERVER", uri?: string, code?: string) {
|
||||
switch (newState) {
|
||||
case "CONNECTION_TYPE":
|
||||
client.write("playerlist_header", {
|
||||
|
@ -226,10 +201,7 @@ export function updateState(
|
|||
});
|
||||
break;
|
||||
case "AUTH":
|
||||
if (code == null || uri == null)
|
||||
throw new Error(
|
||||
"Missing code/uri required for title message type AUTH"
|
||||
);
|
||||
if (code == null || uri == null) throw new Error("Missing code/uri required for title message type AUTH");
|
||||
client.write("playerlist_header", {
|
||||
header: JSON.stringify({
|
||||
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
|
||||
|
@ -245,9 +217,7 @@ export function updateState(
|
|||
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
|
||||
}),
|
||||
footer: JSON.stringify({
|
||||
text: `${Enums.ChatColor.RED}/join <ip>${
|
||||
config.allowCustomPorts ? " [port]" : ""
|
||||
}`,
|
||||
text: `${Enums.ChatColor.RED}/join <ip>${config.allowCustomPorts ? " [port]" : ""}`,
|
||||
}),
|
||||
});
|
||||
break;
|
||||
|
@ -271,10 +241,7 @@ export async function onConnect(client: ClientState) {
|
|||
client.state = ConnectionState.AUTH;
|
||||
client.lastStatusUpdate = Date.now();
|
||||
|
||||
sendMessageWarning(
|
||||
client.gameClient,
|
||||
`WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.`
|
||||
);
|
||||
sendMessageWarning(client.gameClient, `WARNING: This proxy allows you to connect to any 1.8.9 server. Gameplay has shown no major issues, but please note that EaglercraftX may flag some anticheats while playing.`);
|
||||
await new Promise((res) => setTimeout(res, 2000));
|
||||
|
||||
sendMessageWarning(
|
||||
|
@ -283,12 +250,20 @@ export async function onConnect(client: ClientState) {
|
|||
);
|
||||
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.`
|
||||
);
|
||||
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));
|
||||
|
||||
if (config.authentication.enabled) {
|
||||
sendCustomMessage(client.gameClient, "This instance is password-protected. Sign in with /password <password>", "gold");
|
||||
const password = await awaitCommand(client.gameClient, (msg) => msg.startsWith("/password "));
|
||||
if (password === `/password ${config.authentication.password}`) {
|
||||
sendCustomMessage(client.gameClient, "Successfully signed into instance!", "green");
|
||||
} else {
|
||||
client.gameClient.end(Enums.ChatColor.RED + "Bad password!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendCustomMessage(client.gameClient, "What would you like to do?", "gray");
|
||||
sendChatComponent(client.gameClient, {
|
||||
text: "1) ",
|
||||
|
@ -344,11 +319,7 @@ export async function onConnect(client: ClientState) {
|
|||
value: "$3",
|
||||
},
|
||||
});
|
||||
sendCustomMessage(
|
||||
client.gameClient,
|
||||
"Select an option from the above (1 = online, 2 = offline, 3 = EasyMC), either by clicking or manually typing out the option's number on the list.",
|
||||
"green"
|
||||
);
|
||||
sendCustomMessage(client.gameClient, "Select an option from the above (1 = online, 2 = offline, 3 = EasyMC), either by clicking or manually typing out the option's number on the list.", "green");
|
||||
updateState(client.gameClient, "CONNECTION_TYPE");
|
||||
|
||||
let chosenOption: ConnectType | null = null;
|
||||
|
@ -356,11 +327,7 @@ export async function onConnect(client: ClientState) {
|
|||
const option = await awaitCommand(client.gameClient, (msg) => true);
|
||||
switch (option.replace(/\$/gim, "")) {
|
||||
default:
|
||||
sendCustomMessage(
|
||||
client.gameClient,
|
||||
`I don't understand what you meant by "${option}", please reply with a valid option!`,
|
||||
"red"
|
||||
);
|
||||
sendCustomMessage(client.gameClient, `I don't understand what you meant by "${option}", please reply with a valid option!`, "red");
|
||||
break;
|
||||
case "1":
|
||||
chosenOption = ConnectType.ONLINE;
|
||||
|
@ -390,17 +357,8 @@ export async function onConnect(client: ClientState) {
|
|||
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
|
||||
);
|
||||
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);
|
||||
|
@ -415,64 +373,30 @@ export async function onConnect(client: ClientState) {
|
|||
res(result);
|
||||
})
|
||||
);
|
||||
sendMessage(
|
||||
client.gameClient,
|
||||
Enums.ChatColor.BRIGHT_GREEN + "Successfully logged into Minecraft!"
|
||||
);
|
||||
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}.`
|
||||
);
|
||||
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")
|
||||
),
|
||||
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
|
||||
}.`
|
||||
);
|
||||
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>" +
|
||||
(config.allowCustomPorts ? " [port]" : ""),
|
||||
"red"
|
||||
);
|
||||
sendCustomMessage(client.gameClient, "You are not allowed to use custom server ports! /join <ip>" + (config.allowCustomPorts ? " [port]" : ""), "red");
|
||||
host = null;
|
||||
port = null;
|
||||
} else {
|
||||
if (
|
||||
host.match(/^(?:\*\.)?((?!hypixel\.net$)[^.]+\.)*hypixel\.net$/)
|
||||
) {
|
||||
if (host.match(/^(?:\*\.)?((?!hypixel\.net$)[^.]+\.)*hypixel\.net$/) && config.disallowHypixel) {
|
||||
sendCustomMessage(
|
||||
client.gameClient,
|
||||
"Disallowed server, refusing to connect! Hypixel has been known to falsely flag Eaglercraft clients, and thus we do not allow connecting to their server. /join <ip>" +
|
||||
(config.allowCustomPorts ? " [port]" : ""),
|
||||
"Disallowed server, refusing to connect! Hypixel has been known to falsely flag Eaglercraft clients, and thus we do not allow connecting to their server. /join <ip>" + (config.allowCustomPorts ? " [port]" : ""),
|
||||
"red"
|
||||
);
|
||||
} else {
|
||||
|
@ -505,21 +429,10 @@ export async function onConnect(client: ClientState) {
|
|||
},
|
||||
],
|
||||
});
|
||||
logger.info(
|
||||
`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Minecraft account's username (${savedAuth.selectedProfile.name}) using online mode!`
|
||||
);
|
||||
const player = PLUGIN_MANAGER.proxy.players.get(
|
||||
client.gameClient.username
|
||||
);
|
||||
logger.info(`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Minecraft account's username (${savedAuth.selectedProfile.name}) using online mode!`);
|
||||
const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username);
|
||||
player.on("vanillaPacket", (packet, origin) => {
|
||||
if (
|
||||
origin == "CLIENT" &&
|
||||
packet.name == "chat" &&
|
||||
(packet.params.message as string)
|
||||
.toLowerCase()
|
||||
.startsWith("/eag-") &&
|
||||
!packet.cancel
|
||||
) {
|
||||
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||
packet.cancel = true;
|
||||
handleCommand(player, packet.params.message as string);
|
||||
}
|
||||
|
@ -560,13 +473,7 @@ export async function onConnect(client: ClientState) {
|
|||
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?"
|
||||
: ""
|
||||
}`
|
||||
`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?") : ""}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -576,10 +483,7 @@ export async function onConnect(client: ClientState) {
|
|||
client.lastStatusUpdate = Date.now();
|
||||
updateState(client.gameClient, "AUTH_EASYMC");
|
||||
|
||||
sendMessageWarning(
|
||||
client.gameClient,
|
||||
`WARNING: You've chosen to use an account from EasyMC's account pool. Please note that accounts and shared, and may be banned from whatever server you are attempting to join.`
|
||||
);
|
||||
sendMessageWarning(client.gameClient, `WARNING: You've chosen to use an account from EasyMC's account pool. Please note that accounts and shared, and may be banned from whatever server you are attempting to join.`);
|
||||
sendChatComponent(client.gameClient, {
|
||||
text: "Please generate an alt token at ",
|
||||
color: "white",
|
||||
|
@ -621,9 +525,7 @@ export async function onConnect(client: ClientState) {
|
|||
|
||||
let appendOptions: any;
|
||||
while (true) {
|
||||
const tokenResponse = await awaitCommand(client.gameClient, (msg) =>
|
||||
msg.toLowerCase().startsWith("/login")
|
||||
),
|
||||
const tokenResponse = await awaitCommand(client.gameClient, (msg) => msg.toLowerCase().startsWith("/login")),
|
||||
splitResponse = tokenResponse.split(/ /gim, 2).slice(1);
|
||||
if (splitResponse.length != 1) {
|
||||
sendChatComponent(client.gameClient, {
|
||||
|
@ -660,9 +562,7 @@ export async function onConnect(client: ClientState) {
|
|||
color: "white",
|
||||
hoverEvent: {
|
||||
action: "show_text",
|
||||
value:
|
||||
Enums.ChatColor.GOLD +
|
||||
"Click me to open in a new window!",
|
||||
value: Enums.ChatColor.GOLD + "Click me to open in a new window!",
|
||||
},
|
||||
clickEvent: {
|
||||
action: "open_url",
|
||||
|
@ -692,18 +592,10 @@ export async function onConnect(client: ClientState) {
|
|||
],
|
||||
});
|
||||
} else {
|
||||
sendCustomMessage(
|
||||
client.gameClient,
|
||||
"Validating alt token...",
|
||||
"gray"
|
||||
);
|
||||
sendCustomMessage(client.gameClient, "Validating alt token...", "gray");
|
||||
try {
|
||||
appendOptions = await getTokenProfileEasyMc(token);
|
||||
sendCustomMessage(
|
||||
client.gameClient,
|
||||
`Successfully validated your alt token and retrieved your session profile! You'll be joining to your preferred server as ${appendOptions.username}.`,
|
||||
"green"
|
||||
);
|
||||
sendCustomMessage(client.gameClient, `Successfully validated your alt token and retrieved your session profile! You'll be joining to your preferred server as ${appendOptions.username}.`, "green");
|
||||
break;
|
||||
} catch (err) {
|
||||
sendChatComponent(client.gameClient, {
|
||||
|
@ -736,45 +628,18 @@ export async function onConnect(client: ClientState) {
|
|||
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}.`
|
||||
);
|
||||
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")
|
||||
),
|
||||
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
|
||||
}.`
|
||||
);
|
||||
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"
|
||||
);
|
||||
sendCustomMessage(client.gameClient, "You are not allowed to use custom server ports! /join <ip>", "red");
|
||||
host = null;
|
||||
port = null;
|
||||
} else {
|
||||
|
@ -806,21 +671,10 @@ export async function onConnect(client: ClientState) {
|
|||
},
|
||||
],
|
||||
});
|
||||
logger.info(
|
||||
`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their EasyMC alt token's username (${appendOptions.username}) using EasyMC mode!`
|
||||
);
|
||||
const player = PLUGIN_MANAGER.proxy.players.get(
|
||||
client.gameClient.username
|
||||
);
|
||||
logger.info(`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their EasyMC alt token's username (${appendOptions.username}) using EasyMC mode!`);
|
||||
const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username);
|
||||
player.on("vanillaPacket", (packet, origin) => {
|
||||
if (
|
||||
origin == "CLIENT" &&
|
||||
packet.name == "chat" &&
|
||||
(packet.params.message as string)
|
||||
.toLowerCase()
|
||||
.startsWith("/eag-") &&
|
||||
!packet.cancel
|
||||
) {
|
||||
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||
packet.cancel = true;
|
||||
handleCommand(player, packet.params.message as string);
|
||||
}
|
||||
|
@ -843,13 +697,7 @@ export async function onConnect(client: ClientState) {
|
|||
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?"
|
||||
: ""
|
||||
}`
|
||||
`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?") : ""}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -857,45 +705,18 @@ export async function onConnect(client: ClientState) {
|
|||
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}.`
|
||||
);
|
||||
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")
|
||||
),
|
||||
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
|
||||
}.`
|
||||
);
|
||||
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"
|
||||
);
|
||||
sendCustomMessage(client.gameClient, "You are not allowed to use custom server ports! /join <ip>", "red");
|
||||
host = null;
|
||||
port = null;
|
||||
} else {
|
||||
|
@ -927,21 +748,10 @@ export async function onConnect(client: ClientState) {
|
|||
},
|
||||
],
|
||||
});
|
||||
logger.info(
|
||||
`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Eaglercraft username (${client.gameClient.username}) using offline mode!`
|
||||
);
|
||||
const player = PLUGIN_MANAGER.proxy.players.get(
|
||||
client.gameClient.username
|
||||
);
|
||||
logger.info(`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their Eaglercraft username (${client.gameClient.username}) using offline mode!`);
|
||||
const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username);
|
||||
player.on("vanillaPacket", (packet, origin) => {
|
||||
if (
|
||||
origin == "CLIENT" &&
|
||||
packet.name == "chat" &&
|
||||
(packet.params.message as string)
|
||||
.toLowerCase()
|
||||
.startsWith("/eag-") &&
|
||||
!packet.cancel
|
||||
) {
|
||||
if (origin == "CLIENT" && packet.name == "chat" && (packet.params.message as string).toLowerCase().startsWith("/eag-") && !packet.cancel) {
|
||||
packet.cancel = true;
|
||||
handleCommand(player, packet.params.message as string);
|
||||
}
|
||||
|
@ -961,122 +771,32 @@ export async function onConnect(client: ClientState) {
|
|||
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?"
|
||||
: ""
|
||||
}`
|
||||
`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) {
|
||||
if (!client.gameClient.ended) {
|
||||
logger.error(
|
||||
`Error whilst processing user ${client.gameClient.username}: ${
|
||||
err.stack || err
|
||||
}`
|
||||
);
|
||||
client.gameClient.end(
|
||||
Enums.ChatColor.YELLOW +
|
||||
"Something went wrong whilst processing your request. Please reconnect."
|
||||
);
|
||||
logger.error(`Error whilst processing user ${client.gameClient.username}: ${err.stack || err}`);
|
||||
client.gameClient.end(Enums.ChatColor.YELLOW + "Something went wrong whilst processing your request. Please reconnect.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function generateSpawnChunk(): Chunk.PCChunk {
|
||||
const chunk = new (Chunk.default(REGISTRY))(null) as Chunk.PCChunk;
|
||||
chunk.initialize(
|
||||
() =>
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.air.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(8, 64, 8),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.sea_lantern.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(8, 67, 8),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(7, 65, 8),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(7, 66, 8),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(9, 65, 8),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(9, 66, 8),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(8, 65, 7),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(8, 66, 7),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(8, 65, 9),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.setBlock(
|
||||
new Vec3(8, 66, 9),
|
||||
new McBlock(
|
||||
REGISTRY.blocksByName.barrier.id,
|
||||
REGISTRY.biomesByName.the_end.id,
|
||||
0
|
||||
)
|
||||
);
|
||||
chunk.initialize(() => new McBlock(REGISTRY.blocksByName.air.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(8, 64, 8), new McBlock(REGISTRY.blocksByName.sea_lantern.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(8, 67, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(7, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(7, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(9, 65, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(9, 66, 8), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(8, 65, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(8, 66, 7), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(8, 65, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
chunk.setBlock(new Vec3(8, 66, 9), new McBlock(REGISTRY.blocksByName.barrier.id, REGISTRY.biomesByName.the_end.id, 0));
|
||||
// chunk.setBlockLight(new Vec3(8, 65, 8), 15);
|
||||
chunk.setBlockLight(new Vec3(8, 66, 8), 15);
|
||||
return chunk;
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import EventEmitter from "events";
|
||||
import pkg, {
|
||||
Client,
|
||||
ClientOptions,
|
||||
createClient,
|
||||
states,
|
||||
} from "minecraft-protocol";
|
||||
import pkg, { Client, ClientOptions, createClient, states } from "minecraft-protocol";
|
||||
import { WebSocket } from "ws";
|
||||
import { Logger } from "../logger.js";
|
||||
import { Chat } from "./Chat.js";
|
||||
|
@ -15,11 +10,12 @@ import { MineProtocol } from "./Protocol.js";
|
|||
import { EaglerSkins } from "./skins/EaglerSkins.js";
|
||||
import { Util } from "./Util.js";
|
||||
import { BungeeUtil } from "./BungeeUtil.js";
|
||||
import { IncomingMessage } from "http";
|
||||
|
||||
const { createSerializer, createDeserializer } = pkg;
|
||||
|
||||
export class Player extends EventEmitter {
|
||||
public ws: WebSocket;
|
||||
public ws: WebSocket & { httpRequest: IncomingMessage };
|
||||
public username?: string;
|
||||
public skin?: EaglerSkins.EaglerSkin;
|
||||
public uuid?: string;
|
||||
|
@ -37,14 +33,13 @@ export class Player extends EventEmitter {
|
|||
public clientDeserializer: any;
|
||||
private _kickMessage: string;
|
||||
|
||||
constructor(ws: WebSocket, playerName?: string, serverConnection?: Client) {
|
||||
constructor(ws: WebSocket & { httpRequest: IncomingMessage }, 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);
|
||||
if (this.username != null) this.uuid = Util.generateUUIDFromPlayer(this.username);
|
||||
this.serverSerializer = createSerializer({
|
||||
state: states.PLAY,
|
||||
isServer: true,
|
||||
|
@ -81,28 +76,15 @@ export class Player extends EventEmitter {
|
|||
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.`
|
||||
);
|
||||
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.`
|
||||
);
|
||||
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) {
|
||||
|
@ -113,10 +95,7 @@ export class Player extends EventEmitter {
|
|||
} else {
|
||||
try {
|
||||
const parsed = this.serverDeserializer.parsePacketBuffer(msg)?.data,
|
||||
translated = this.translator.translatePacketClient(
|
||||
parsed.params,
|
||||
parsed
|
||||
),
|
||||
translated = this.translator.translatePacketClient(parsed.params, parsed),
|
||||
packetData = {
|
||||
name: translated[0],
|
||||
params: translated[1],
|
||||
|
@ -132,12 +111,7 @@ export class Player extends EventEmitter {
|
|||
);
|
||||
}
|
||||
} catch (err) {
|
||||
this._logger.debug(
|
||||
`Client ${this
|
||||
.username!} sent an unrecognized packet that could not be parsed!\n${
|
||||
err.stack ?? err
|
||||
}`
|
||||
);
|
||||
this._logger.debug(`Client ${this.username!} sent an unrecognized packet that could not be parsed!\n${err.stack ?? err}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -147,21 +121,12 @@ export class Player extends EventEmitter {
|
|||
this.ws.send(packet.serialize());
|
||||
}
|
||||
|
||||
public async read(
|
||||
packetId?: Enums.PacketId,
|
||||
filter?: (packet: Packet) => boolean
|
||||
): Promise<Packet> {
|
||||
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
|
||||
) {
|
||||
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 {
|
||||
|
@ -188,16 +153,7 @@ export class Player extends EventEmitter {
|
|||
|
||||
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.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();
|
||||
|
@ -208,10 +164,7 @@ export class Player extends EventEmitter {
|
|||
}
|
||||
|
||||
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.`
|
||||
);
|
||||
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(
|
||||
|
@ -269,9 +222,7 @@ export class Player extends EventEmitter {
|
|||
)
|
||||
);
|
||||
|
||||
await this._bindListenersMineClient(this.serverConnection, true, () =>
|
||||
oldConnection.end()
|
||||
)
|
||||
await this._bindListenersMineClient(this.serverConnection, true, () => oldConnection.end())
|
||||
.then(() => {
|
||||
this.emit("switchServer", this.serverConnection, this);
|
||||
res();
|
||||
|
@ -283,11 +234,7 @@ export class Player extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
private async _bindListenersMineClient(
|
||||
client: Client,
|
||||
switchingServers?: boolean,
|
||||
onSwitch?: Function
|
||||
) {
|
||||
private async _bindListenersMineClient(client: Client, switchingServers?: boolean, onSwitch?: Function) {
|
||||
return new Promise((res, rej) => {
|
||||
let stream = false,
|
||||
uuid;
|
||||
|
@ -300,17 +247,13 @@ export class Player extends EventEmitter {
|
|||
if (!stream) {
|
||||
rej(err);
|
||||
} 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 && this.state != Enums.ClientState.DISCONNECTED) {
|
||||
client.end("Timed out waiting for server connection.");
|
||||
this.disconnect(
|
||||
Enums.ChatColor.RED + "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);
|
||||
|
@ -349,14 +292,8 @@ export class Player extends EventEmitter {
|
|||
if (!stream) {
|
||||
if (switchingServers) {
|
||||
if (meta.name == "login" && meta.state == states.PLAY && uuid) {
|
||||
this.translator = new BungeeUtil.PacketUUIDTranslator(
|
||||
client.uuid,
|
||||
this.uuid
|
||||
);
|
||||
const pckSeq = BungeeUtil.getRespawnSequence(
|
||||
packet,
|
||||
this.serverSerializer
|
||||
);
|
||||
this.translator = new BungeeUtil.PacketUUIDTranslator(client.uuid, this.uuid);
|
||||
const pckSeq = BungeeUtil.getRespawnSequence(packet, this.serverSerializer);
|
||||
this.ws.send(
|
||||
this.serverSerializer.createPacketBuffer({
|
||||
name: "login",
|
||||
|
@ -367,19 +304,12 @@ export class Player extends EventEmitter {
|
|||
stream = true;
|
||||
if (onSwitch) onSwitch();
|
||||
res(null);
|
||||
} else if (
|
||||
meta.name == "success" &&
|
||||
meta.state == states.LOGIN &&
|
||||
!uuid
|
||||
) {
|
||||
} else if (meta.name == "success" && meta.state == states.LOGIN && !uuid) {
|
||||
uuid = packet.uuid;
|
||||
}
|
||||
} else {
|
||||
if (meta.name == "login" && meta.state == states.PLAY && uuid) {
|
||||
this.translator = new BungeeUtil.PacketUUIDTranslator(
|
||||
client.uuid,
|
||||
this.uuid
|
||||
);
|
||||
this.translator = new BungeeUtil.PacketUUIDTranslator(client.uuid, this.uuid);
|
||||
this.ws.send(
|
||||
this.serverSerializer.createPacketBuffer({
|
||||
name: "login",
|
||||
|
@ -389,19 +319,12 @@ export class Player extends EventEmitter {
|
|||
stream = true;
|
||||
if (onSwitch) onSwitch();
|
||||
res(null);
|
||||
} else if (
|
||||
meta.name == "success" &&
|
||||
meta.state == states.LOGIN &&
|
||||
!uuid
|
||||
) {
|
||||
} else if (meta.name == "success" && meta.state == states.LOGIN && !uuid) {
|
||||
uuid = packet.uuid;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const translated = this.translator!.translatePacketServer(
|
||||
packet,
|
||||
meta
|
||||
),
|
||||
const translated = this.translator!.translatePacketServer(packet, meta),
|
||||
eventData = {
|
||||
name: translated[0],
|
||||
params: translated[1],
|
||||
|
@ -444,8 +367,5 @@ export declare interface Player {
|
|||
on<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
|
||||
once<U extends keyof PlayerEvents>(event: U, listener: PlayerEvents[U]): this;
|
||||
|
||||
emit<U extends keyof PlayerEvents>(
|
||||
event: U,
|
||||
...args: Parameters<PlayerEvents[U]>
|
||||
): boolean;
|
||||
emit<U extends keyof PlayerEvents>(event: U, ...args: Parameters<PlayerEvents[U]>): boolean;
|
||||
}
|
||||
|
|
|
@ -13,12 +13,7 @@ import SCIdentifyPacket from "./packets/SCIdentifyPacket.js";
|
|||
import { Motd } from "./Motd.js";
|
||||
import { Player } from "./Player.js";
|
||||
import { Enums } from "./Enums.js";
|
||||
import {
|
||||
NETWORK_VERSION,
|
||||
PROXY_BRANDING,
|
||||
PROXY_VERSION,
|
||||
VANILLA_PROTOCOL_VERSION,
|
||||
} from "../meta.js";
|
||||
import { NETWORK_VERSION, PROXY_BRANDING, PROXY_VERSION, VANILLA_PROTOCOL_VERSION } from "../meta.js";
|
||||
import { CSUsernamePacket } from "./packets/CSUsernamePacket.js";
|
||||
import { SCSyncUuidPacket } from "./packets/SCSyncUuidPacket.js";
|
||||
import { SCReadyPacket } from "./packets/SCReadyPacket.js";
|
||||
|
@ -61,42 +56,30 @@ export class Proxy extends EventEmitter {
|
|||
// hijack the initial handler logger to append [InitialHandler] to the beginning
|
||||
(this.initalHandlerLogger as any)._info = this.initalHandlerLogger.info;
|
||||
this.initalHandlerLogger.info = (msg: string) => {
|
||||
(this.initalHandlerLogger as any)._info(
|
||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
||||
);
|
||||
(this.initalHandlerLogger as any)._info(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||
};
|
||||
(this.initalHandlerLogger as any)._warn = this.initalHandlerLogger.warn;
|
||||
this.initalHandlerLogger.warn = (msg: string) => {
|
||||
(this.initalHandlerLogger as any)._warn(
|
||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
||||
);
|
||||
(this.initalHandlerLogger as any)._warn(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||
};
|
||||
(this.initalHandlerLogger as any)._error = this.initalHandlerLogger.error;
|
||||
this.initalHandlerLogger.error = (msg: string) => {
|
||||
(this.initalHandlerLogger as any)._error(
|
||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
||||
);
|
||||
(this.initalHandlerLogger as any)._error(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||
};
|
||||
(this.initalHandlerLogger as any)._fatal = this.initalHandlerLogger.fatal;
|
||||
this.initalHandlerLogger.fatal = (msg: string) => {
|
||||
(this.initalHandlerLogger as any)._fatal(
|
||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
||||
);
|
||||
(this.initalHandlerLogger as any)._fatal(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||
};
|
||||
(this.initalHandlerLogger as any)._debug = this.initalHandlerLogger.debug;
|
||||
this.initalHandlerLogger.debug = (msg: string) => {
|
||||
(this.initalHandlerLogger as any)._debug(
|
||||
`${chalk.blue("[InitialHandler]")} ${msg}`
|
||||
);
|
||||
(this.initalHandlerLogger as any)._debug(`${chalk.blue("[InitialHandler]")} ${msg}`);
|
||||
};
|
||||
this.config = config;
|
||||
this.pluginManager = pluginManager;
|
||||
instanceCount++;
|
||||
|
||||
process.on("uncaughtException", (err) => {
|
||||
this._logger.warn(
|
||||
`An uncaught exception was caught! Error: ${err.stack}`
|
||||
);
|
||||
this._logger.warn(`An uncaught exception was caught! Error: ${err.stack}`);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (err) => {
|
||||
|
@ -107,16 +90,10 @@ export class Proxy extends EventEmitter {
|
|||
public async init() {
|
||||
this._logger.info(`Starting ${PROXY_BRANDING} v${PROXY_VERSION}...`);
|
||||
global.PROXY = this;
|
||||
if (this.loaded)
|
||||
throw new Error(
|
||||
"Can't initiate if proxy instance is already initialized or is being initialized!"
|
||||
);
|
||||
if (this.loaded) throw new Error("Can't initiate if proxy instance is already initialized or is being initialized!");
|
||||
this.loaded = true;
|
||||
this.packetRegistry = await loadPackets();
|
||||
this.skinServer = new EaglerSkins.SkinServer(
|
||||
this,
|
||||
this.config.skinUrlWhitelist
|
||||
);
|
||||
this.skinServer = new EaglerSkins.SkinServer(this, this.config.skinUrlWhitelist);
|
||||
global.PACKET_REGISTRY = this.packetRegistry;
|
||||
if (this.config.motd == "FORWARD") {
|
||||
this._pollServer(this.config.server.host, this.config.server.port);
|
||||
|
@ -136,22 +113,12 @@ export class Proxy extends EventEmitter {
|
|||
},
|
||||
(req, res) => this._handleNonWSRequest(req, res, this.config)
|
||||
)
|
||||
.listen(
|
||||
this.config.bindPort || 8080,
|
||||
this.config.bindHost || "127.0.0.1"
|
||||
);
|
||||
.listen(this.config.bindPort || 8080, this.config.bindHost || "127.0.0.1");
|
||||
this.wsServer = new WebSocketServer({
|
||||
noServer: true,
|
||||
});
|
||||
} else {
|
||||
this.httpServer = http
|
||||
.createServer((req, res) =>
|
||||
this._handleNonWSRequest(req, res, this.config)
|
||||
)
|
||||
.listen(
|
||||
this.config.bindPort || 8080,
|
||||
this.config.bindHost || "127.0.0.1"
|
||||
);
|
||||
this.httpServer = http.createServer((req, res) => this._handleNonWSRequest(req, res, this.config)).listen(this.config.bindPort || 8080, this.config.bindHost || "127.0.0.1");
|
||||
this.wsServer = new WebSocketServer({
|
||||
noServer: true,
|
||||
});
|
||||
|
@ -166,73 +133,49 @@ export class Proxy extends EventEmitter {
|
|||
try {
|
||||
await this._handleWSConnectionReq(r, s, h);
|
||||
} catch (err) {
|
||||
this._logger.error(
|
||||
`Error was caught whilst trying to handle WebSocket upgrade! Error: ${
|
||||
err.stack ?? err
|
||||
}`
|
||||
);
|
||||
this._logger.error(`Error was caught whilst trying to handle WebSocket upgrade! Error: ${err.stack ?? err}`);
|
||||
}
|
||||
});
|
||||
this.pluginManager.emit("proxyFinishLoading", this, this.pluginManager);
|
||||
this._logger.info(
|
||||
`Started WebSocket server and binded to ${this.config.bindHost} on port ${this.config.bindPort}.`
|
||||
);
|
||||
this._logger.info(`Started WebSocket server and binded to ${this.config.bindHost} on port ${this.config.bindPort}.`);
|
||||
}
|
||||
|
||||
private _handleNonWSRequest(
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
config: Config["adapter"]
|
||||
) {
|
||||
res
|
||||
.setHeader("Content-Type", "text/html")
|
||||
.writeHead(426)
|
||||
.end(UPGRADE_REQUIRED_RESPONSE);
|
||||
private _handleNonWSRequest(req: http.IncomingMessage, res: http.ServerResponse, config: Config["adapter"]) {
|
||||
const ctx: Util.Handlable = { handled: false };
|
||||
this.emit("httpConnection", req, res, ctx);
|
||||
if (!ctx.handled) res.setHeader("Content-Type", "text/html").writeHead(426).end(UPGRADE_REQUIRED_RESPONSE);
|
||||
}
|
||||
|
||||
readonly LOGIN_TIMEOUT = 30000;
|
||||
|
||||
private async _handleWSConnection(ws: WebSocket) {
|
||||
private async _handleWSConnection(ws: WebSocket, req: http.IncomingMessage) {
|
||||
const firstPacket = await Util.awaitPacket(ws);
|
||||
let player: Player, handled: boolean;
|
||||
setTimeout(() => {
|
||||
if (!handled) {
|
||||
this.initalHandlerLogger.warn(
|
||||
`Disconnecting client ${
|
||||
player
|
||||
? player.username ??
|
||||
`[/${(ws as any)._socket.remoteAddress}:${
|
||||
(ws as any)._socket.remotePort
|
||||
}`
|
||||
: `[/${(ws as any)._socket.remoteAddress}:${
|
||||
(ws as any)._socket.remotePort
|
||||
}`
|
||||
player ? player.username ?? `[/${(ws as any)._socket.remoteAddress}:${(ws as any)._socket.remotePort}` : `[/${(ws as any)._socket.remoteAddress}:${(ws as any)._socket.remotePort}`
|
||||
} due to connection timing out.`
|
||||
);
|
||||
if (player)
|
||||
player.disconnect(
|
||||
`${Enums.ChatColor.YELLOW} Your connection timed out whilst processing handshake, please try again.`
|
||||
);
|
||||
if (player) player.disconnect(`${Enums.ChatColor.YELLOW} Your connection timed out whilst processing handshake, please try again.`);
|
||||
else ws.close();
|
||||
}
|
||||
}, this.LOGIN_TIMEOUT);
|
||||
try {
|
||||
const ctx: Util.Handlable = { handled: false };
|
||||
await this.emit("wsConnection", ws, req, ctx);
|
||||
if (ctx.handled) return;
|
||||
|
||||
if (firstPacket.toString() === "Accept: MOTD") {
|
||||
if (this.broadcastMotd) {
|
||||
if ((this.broadcastMotd as any)._static) {
|
||||
this.broadcastMotd.jsonMotd.data.online = this.players.size;
|
||||
// sample for players
|
||||
this.broadcastMotd.jsonMotd.data.players = [];
|
||||
const playerSample = [...this.players.keys()]
|
||||
.filter((sample) => !sample.startsWith("!phs_"))
|
||||
.slice(0, 5);
|
||||
const playerSample = [...this.players.keys()].filter((sample) => !sample.startsWith("!phs_")).slice(0, 5);
|
||||
this.broadcastMotd.jsonMotd.data.players = playerSample;
|
||||
if (this.players.size - playerSample.length > 0)
|
||||
this.broadcastMotd.jsonMotd.data.players.push(
|
||||
`${Enums.ChatColor.GRAY}${Enums.ChatColor.ITALIC}(and ${
|
||||
this.players.size - playerSample.length
|
||||
} more)`
|
||||
);
|
||||
if (this.players.size - playerSample.length > 0) this.broadcastMotd.jsonMotd.data.players.push(`${Enums.ChatColor.GRAY}${Enums.ChatColor.ITALIC}(and ${this.players.size - playerSample.length} more)`);
|
||||
|
||||
const bufferized = this.broadcastMotd.toBuffer();
|
||||
ws.send(bufferized[0]);
|
||||
|
@ -246,24 +189,15 @@ export class Proxy extends EventEmitter {
|
|||
handled = true;
|
||||
ws.close();
|
||||
} else {
|
||||
player = new Player(ws);
|
||||
(ws as any).httpRequest = req;
|
||||
player = new Player(ws as any);
|
||||
const loginPacket = new CSLoginPacket().deserialize(firstPacket);
|
||||
player.state = Enums.ClientState.PRE_HANDSHAKE;
|
||||
if (loginPacket.gameVersion != VANILLA_PROTOCOL_VERSION) {
|
||||
player.disconnect(
|
||||
`${Enums.ChatColor.RED}Please connect to this proxy on EaglercraftX 1.8.9.`
|
||||
);
|
||||
player.disconnect(`${Enums.ChatColor.RED}Please connect to this proxy on EaglercraftX 1.8.9.`);
|
||||
return;
|
||||
} else if (loginPacket.networkVersion != NETWORK_VERSION) {
|
||||
player.disconnect(
|
||||
`${Enums.ChatColor.RED}Your EaglercraftX version is too ${
|
||||
loginPacket.networkVersion > NETWORK_VERSION ? "new" : "old"
|
||||
}! Please ${
|
||||
loginPacket.networkVersion > NETWORK_VERSION
|
||||
? "downgrade"
|
||||
: "update"
|
||||
}.`
|
||||
);
|
||||
player.disconnect(`${Enums.ChatColor.RED}Your EaglercraftX version is too ${loginPacket.networkVersion > NETWORK_VERSION ? "new" : "old"}! Please ${loginPacket.networkVersion > NETWORK_VERSION ? "downgrade" : "update"}.`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
@ -275,35 +209,22 @@ export class Proxy extends EventEmitter {
|
|||
player.username = loginPacket.username;
|
||||
player.uuid = Util.generateUUIDFromPlayer(player.username);
|
||||
if (this.players.size > this.config.maxConcurrentClients) {
|
||||
player.disconnect(
|
||||
`${Enums.ChatColor.YELLOW}Proxy is full! Please try again later.`
|
||||
);
|
||||
player.disconnect(`${Enums.ChatColor.YELLOW}Proxy is full! Please try again later.`);
|
||||
return;
|
||||
} else if (
|
||||
this.players.get(player.username) != null ||
|
||||
this.players.get(`!phs.${player.uuid}`) != null
|
||||
) {
|
||||
player.disconnect(
|
||||
`${Enums.ChatColor.YELLOW}Someone under your username (${player.username}) is already connected to the proxy!`
|
||||
);
|
||||
} else if (this.players.get(player.username) != null || this.players.get(`!phs.${player.uuid}`) != null) {
|
||||
player.disconnect(`${Enums.ChatColor.YELLOW}Someone under your username (${player.username}) is already connected to the proxy!`);
|
||||
return;
|
||||
}
|
||||
this.players.set(`!phs.${player.uuid}`, player);
|
||||
this._logger.info(
|
||||
`Player ${loginPacket.username} (${Util.generateUUIDFromPlayer(
|
||||
loginPacket.username
|
||||
)}) running ${loginPacket.brand}/${loginPacket.version} (net ver: ${
|
||||
loginPacket.networkVersion
|
||||
}, game ver: ${loginPacket.gameVersion}) is attempting to connect!`
|
||||
`Player ${loginPacket.username} (${Util.generateUUIDFromPlayer(loginPacket.username)}) running ${loginPacket.brand}/${loginPacket.version} (net ver: ${loginPacket.networkVersion}, game ver: ${
|
||||
loginPacket.gameVersion
|
||||
}) is attempting to connect!`
|
||||
);
|
||||
player.write(new SCIdentifyPacket());
|
||||
const usernamePacket: CSUsernamePacket = (await player.read(
|
||||
Enums.PacketId.CSUsernamePacket
|
||||
)) as any;
|
||||
const usernamePacket: CSUsernamePacket = (await player.read(Enums.PacketId.CSUsernamePacket)) as any;
|
||||
if (usernamePacket.username !== player.username) {
|
||||
player.disconnect(
|
||||
`${Enums.ChatColor.YELLOW}Failed to complete handshake. Your game version may be too old or too new.`
|
||||
);
|
||||
player.disconnect(`${Enums.ChatColor.YELLOW}Failed to complete handshake. Your game version may be too old or too new.`);
|
||||
return;
|
||||
}
|
||||
const syncUuid = new SCSyncUuidPacket();
|
||||
|
@ -311,12 +232,7 @@ export class Proxy extends EventEmitter {
|
|||
syncUuid.uuid = player.uuid;
|
||||
player.write(syncUuid);
|
||||
|
||||
const prom = await Promise.all([
|
||||
player.read(Enums.PacketId.CSReadyPacket),
|
||||
(await player.read(
|
||||
Enums.PacketId.CSSetSkinPacket
|
||||
)) as CSSetSkinPacket,
|
||||
]),
|
||||
const prom = await Promise.all([player.read(Enums.PacketId.CSReadyPacket), (await player.read(Enums.PacketId.CSSetSkinPacket)) as CSSetSkinPacket]),
|
||||
skin = prom[1],
|
||||
obj = new EaglerSkins.EaglerSkin();
|
||||
obj.owner = player;
|
||||
|
@ -331,45 +247,31 @@ export class Proxy extends EventEmitter {
|
|||
player.initListeners();
|
||||
this._bindListenersToPlayer(player);
|
||||
player.state = Enums.ClientState.POST_HANDSHAKE;
|
||||
this._logger.info(
|
||||
`Handshake Success! Connecting player ${player.username} to server...`
|
||||
);
|
||||
this._logger.info(`Handshake Success! Connecting player ${player.username} to server...`);
|
||||
handled = true;
|
||||
await player.connect({
|
||||
host: this.config.server.host,
|
||||
port: this.config.server.port,
|
||||
username: player.username,
|
||||
});
|
||||
this._logger.info(
|
||||
`Player ${player.username} successfully connected to server.`
|
||||
);
|
||||
this._logger.info(`Player ${player.username} successfully connected to server.`);
|
||||
this.emit("playerConnect", player);
|
||||
}
|
||||
} catch (err) {
|
||||
this.initalHandlerLogger.warn(
|
||||
`Error occurred whilst handling handshake: ${err.stack ?? err}`
|
||||
);
|
||||
this.initalHandlerLogger.warn(`Error occurred whilst handling handshake: ${err.stack ?? err}`);
|
||||
handled = true;
|
||||
ws.close();
|
||||
if (player && player.uuid && this.players.has(`!phs.${player.uuid}`))
|
||||
this.players.delete(`!phs.${player.uuid}`);
|
||||
if (player && player.uuid && this.players.has(player.username))
|
||||
this.players.delete(player.username);
|
||||
if (player && player.uuid && this.players.has(`!phs.${player.uuid}`)) this.players.delete(`!phs.${player.uuid}`);
|
||||
if (player && player.uuid && this.players.has(player.username)) this.players.delete(player.username);
|
||||
}
|
||||
}
|
||||
|
||||
private _bindListenersToPlayer(player: Player) {
|
||||
let sentDisconnectMsg = false;
|
||||
player.on("disconnect", () => {
|
||||
if (this.players.has(player.username))
|
||||
this.players.delete(player.username);
|
||||
this.initalHandlerLogger.info(
|
||||
`DISCONNECT ${player.username} <=> DISCONNECTED`
|
||||
);
|
||||
if (!sentDisconnectMsg)
|
||||
this._logger.info(
|
||||
`Player ${player.username} (${player.uuid}) disconnected from the proxy server.`
|
||||
);
|
||||
if (this.players.has(player.username)) this.players.delete(player.username);
|
||||
this.initalHandlerLogger.info(`DISCONNECT ${player.username} <=> DISCONNECTED`);
|
||||
if (!sentDisconnectMsg) this._logger.info(`Player ${player.username} (${player.uuid}) disconnected from the proxy server.`);
|
||||
});
|
||||
player.on("proxyPacket", async (packet) => {
|
||||
if (packet.packetId == Enums.PacketId.CSChannelMessagePacket) {
|
||||
|
@ -379,23 +281,15 @@ export class Proxy extends EventEmitter {
|
|||
await this.skinServer.handleRequest(msg, player);
|
||||
}
|
||||
} catch (err) {
|
||||
this._logger.error(
|
||||
`Failed to process channel message packet! Error: ${
|
||||
err.stack || err
|
||||
}`
|
||||
);
|
||||
this._logger.error(`Failed to process channel message packet! Error: ${err.stack || err}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
player.on("switchServer", (client) => {
|
||||
this.initalHandlerLogger.info(
|
||||
`SWITCH_SERVER ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`
|
||||
);
|
||||
this.initalHandlerLogger.info(`SWITCH_SERVER ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`);
|
||||
});
|
||||
player.on("joinServer", (client) => {
|
||||
this.initalHandlerLogger.info(
|
||||
`SERVER_CONNECTED ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`
|
||||
);
|
||||
this.initalHandlerLogger.info(`SERVER_CONNECTED ${player.username} <=> ${client.socket.remoteAddress}:${client.socket.remotePort}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -404,62 +298,33 @@ export class Proxy extends EventEmitter {
|
|||
private _pollServer(host: string, port: number, interval?: number) {
|
||||
(async () => {
|
||||
while (true) {
|
||||
const motd = await Motd.MOTD.generateMOTDFromPing(host, port).catch(
|
||||
(err) => {
|
||||
this._logger.warn(
|
||||
`Error polling ${host}:${port} for MOTD: ${err.stack ?? err}`
|
||||
);
|
||||
}
|
||||
);
|
||||
const motd = await Motd.MOTD.generateMOTDFromPing(host, port).catch((err) => {
|
||||
this._logger.warn(`Error polling ${host}:${port} for MOTD: ${err.stack ?? err}`);
|
||||
});
|
||||
if (motd) this.broadcastMotd = motd;
|
||||
await new Promise((res) =>
|
||||
setTimeout(res, interval ?? Proxy.POLL_INTERVAL)
|
||||
);
|
||||
await new Promise((res) => setTimeout(res, interval ?? Proxy.POLL_INTERVAL));
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
private async _handleWSConnectionReq(
|
||||
req: http.IncomingMessage,
|
||||
socket: Duplex,
|
||||
head: Buffer
|
||||
) {
|
||||
const origin =
|
||||
req.headers.origin == null || req.headers.origin == "null"
|
||||
? null
|
||||
: req.headers.origin;
|
||||
private async _handleWSConnectionReq(req: http.IncomingMessage, socket: Duplex, head: Buffer) {
|
||||
const origin = req.headers.origin == null || req.headers.origin == "null" ? null : req.headers.origin;
|
||||
if (!this.config.origins.allowOfflineDownloads && origin == null) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.config.origins.originBlacklist != null &&
|
||||
this.config.origins.originBlacklist.some((host) =>
|
||||
Util.areDomainsEqual(host, origin)
|
||||
)
|
||||
) {
|
||||
if (this.config.origins.originBlacklist != null && this.config.origins.originBlacklist.some((host) => Util.areDomainsEqual(host, origin))) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.config.origins.originWhitelist != null &&
|
||||
!this.config.origins.originWhitelist.some((host) =>
|
||||
Util.areDomainsEqual(host, origin)
|
||||
)
|
||||
) {
|
||||
if (this.config.origins.originWhitelist != null && !this.config.origins.originWhitelist.some((host) => Util.areDomainsEqual(host, origin))) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.wsServer.handleUpgrade(req, socket, head, (ws) =>
|
||||
this._handleWSConnection(ws)
|
||||
);
|
||||
await this.wsServer.handleUpgrade(req, socket, head, (ws) => this._handleWSConnection(ws, req));
|
||||
} catch (err) {
|
||||
this._logger.error(
|
||||
`Error was caught whilst trying to handle WebSocket connection request! Error: ${
|
||||
err.stack ?? err
|
||||
}`
|
||||
);
|
||||
this._logger.error(`Error was caught whilst trying to handle WebSocket connection request! Error: ${err.stack ?? err}`);
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -475,13 +340,12 @@ export class Proxy extends EventEmitter {
|
|||
interface ProxyEvents {
|
||||
playerConnect: (player: Player) => void;
|
||||
playerDisconnect: (player: Player) => void;
|
||||
|
||||
httpConnection: (req: http.IncomingMessage, res: http.ServerResponse, ctx: Util.Handlable) => void;
|
||||
wsConnection: (ws: WebSocket, req: http.IncomingMessage, ctx: Util.Handlable) => void;
|
||||
}
|
||||
|
||||
export declare interface Proxy {
|
||||
on<U extends keyof ProxyEvents>(event: U, listener: ProxyEvents[U]): this;
|
||||
|
||||
emit<U extends keyof ProxyEvents>(
|
||||
event: U,
|
||||
...args: Parameters<ProxyEvents[U]>
|
||||
): boolean;
|
||||
emit<U extends keyof ProxyEvents>(event: U, ...args: Parameters<ProxyEvents[U]>): boolean;
|
||||
}
|
||||
|
|
|
@ -37,26 +37,18 @@ export namespace Util {
|
|||
export function uuidStringToBuffer(uuid: string): Buffer {
|
||||
if (!uuid) return Buffer.alloc(16); // Return empty buffer
|
||||
const hexStr = uuid.replace(/-/g, "");
|
||||
if (uuid.length != 36 || hexStr.length != 32)
|
||||
throw new Error(`Invalid UUID string: ${uuid}`);
|
||||
if (uuid.length != 36 || hexStr.length != 32) throw new Error(`Invalid UUID string: ${uuid}`);
|
||||
return Buffer.from(hexStr, "hex");
|
||||
}
|
||||
|
||||
export function uuidBufferToString(buffer: Buffer): string {
|
||||
if (buffer.length != 16)
|
||||
throw new Error(`Invalid buffer length for uuid: ${buffer.length}`);
|
||||
if (buffer.length != 16) throw new Error(`Invalid buffer length for uuid: ${buffer.length}`);
|
||||
if (buffer.equals(Buffer.alloc(16))) return null; // If buffer is all zeros, return null
|
||||
const str = buffer.toString("hex");
|
||||
return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(
|
||||
12,
|
||||
16
|
||||
)}-${str.slice(16, 20)}-${str.slice(20)}`;
|
||||
return `${str.slice(0, 8)}-${str.slice(8, 12)}-${str.slice(12, 16)}-${str.slice(16, 20)}-${str.slice(20)}`;
|
||||
}
|
||||
|
||||
export function awaitPacket(
|
||||
ws: WebSocket,
|
||||
filter?: (msg: Buffer) => boolean
|
||||
): Promise<Buffer> {
|
||||
export function awaitPacket(ws: WebSocket, filter?: (msg: Buffer) => boolean): Promise<Buffer> {
|
||||
return new Promise<Buffer>((res, rej) => {
|
||||
let resolved = false;
|
||||
const msgCb = (msg: any) => {
|
||||
|
@ -64,17 +56,13 @@ export namespace Util {
|
|||
resolved = true;
|
||||
ws.removeListener("message", msgCb);
|
||||
ws.removeListener("close", discon);
|
||||
ws.setMaxListeners(
|
||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
||||
);
|
||||
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||
res(msg);
|
||||
} else if (filter == null) {
|
||||
resolved = true;
|
||||
ws.removeListener("message", msgCb);
|
||||
ws.removeListener("close", discon);
|
||||
ws.setMaxListeners(
|
||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
||||
);
|
||||
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||
res(msg);
|
||||
}
|
||||
};
|
||||
|
@ -82,9 +70,7 @@ export namespace Util {
|
|||
resolved = true;
|
||||
ws.removeListener("message", msgCb);
|
||||
ws.removeListener("close", discon);
|
||||
ws.setMaxListeners(
|
||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
||||
);
|
||||
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||
rej("Connection closed");
|
||||
};
|
||||
ws.setMaxListeners(ws.getMaxListeners() + 2);
|
||||
|
@ -93,9 +79,7 @@ export namespace Util {
|
|||
setTimeout(() => {
|
||||
ws.removeListener("message", msgCb);
|
||||
ws.removeListener("close", discon);
|
||||
ws.setMaxListeners(
|
||||
ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2
|
||||
);
|
||||
ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2);
|
||||
rej("Timed out");
|
||||
}, 10000);
|
||||
});
|
||||
|
@ -104,57 +88,28 @@ export namespace Util {
|
|||
export function validateUsername(user: string): void | never {
|
||||
if (user.length > 20) throw new Error("Username is too long!");
|
||||
if (user.length < 3) throw new Error("Username is too short!");
|
||||
if (!!user.match(USERNAME_REGEX))
|
||||
throw new Error(
|
||||
"Invalid username. Username can only contain alphanumeric characters, and the underscore (_) character."
|
||||
);
|
||||
if (!!user.match(USERNAME_REGEX)) throw new Error("Invalid username. Username can only contain alphanumeric characters, and the underscore (_) character.");
|
||||
}
|
||||
|
||||
export function areDomainsEqual(d1: string, d2: string): boolean {
|
||||
if (d1.endsWith("*."))
|
||||
d1 = d1.replace(
|
||||
"*.",
|
||||
"WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION."
|
||||
);
|
||||
if (d1.endsWith("*.")) d1 = d1.replace("*.", "WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION.");
|
||||
const parseResult1 = parseDomain(d1),
|
||||
parseResult2 = parseDomain(d2);
|
||||
if (
|
||||
parseResult1.type != ParseResultType.Invalid &&
|
||||
parseResult2.type != ParseResultType.Invalid
|
||||
) {
|
||||
if (
|
||||
parseResult1.type == ParseResultType.Ip &&
|
||||
parseResult2.type == ParseResultType.Ip
|
||||
) {
|
||||
if (parseResult1.type != ParseResultType.Invalid && parseResult2.type != ParseResultType.Invalid) {
|
||||
if (parseResult1.type == ParseResultType.Ip && parseResult2.type == ParseResultType.Ip) {
|
||||
return parseResult1.hostname == parseResult2.hostname ? true : false;
|
||||
} else if (
|
||||
parseResult1.type == ParseResultType.Listed &&
|
||||
parseResult2.type == ParseResultType.Listed
|
||||
) {
|
||||
if (
|
||||
parseResult1.subDomains[0] ==
|
||||
"WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION"
|
||||
) {
|
||||
} else if (parseResult1.type == ParseResultType.Listed && parseResult2.type == ParseResultType.Listed) {
|
||||
if (parseResult1.subDomains[0] == "WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION") {
|
||||
// wildcard
|
||||
const domainPlusTld1 =
|
||||
parseResult1.domain +
|
||||
("." + parseResult1.topLevelDomains.join("."));
|
||||
const domainPlusTld2 =
|
||||
parseResult2.domain +
|
||||
("." + parseResult2.topLevelDomains.join("."));
|
||||
const domainPlusTld1 = parseResult1.domain + ("." + parseResult1.topLevelDomains.join("."));
|
||||
const domainPlusTld2 = parseResult2.domain + ("." + parseResult2.topLevelDomains.join("."));
|
||||
return domainPlusTld1 == domainPlusTld2 ? true : false;
|
||||
} else {
|
||||
// no wildcard
|
||||
return d1 == d2 ? true : false;
|
||||
}
|
||||
} else if (
|
||||
parseResult1.type == ParseResultType.NotListed &&
|
||||
parseResult2.type == ParseResultType.NotListed
|
||||
) {
|
||||
if (
|
||||
parseResult1.labels[0] ==
|
||||
"WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION"
|
||||
) {
|
||||
} else if (parseResult1.type == ParseResultType.NotListed && parseResult2.type == ParseResultType.NotListed) {
|
||||
if (parseResult1.labels[0] == "WILDCARD-LOL-EXTRA-LONG-SUBDOMAIN-TO-LOWER-CHANCES-OF-COLLISION") {
|
||||
// wildcard
|
||||
const domainPlusTld1 = parseResult1.labels.slice(2).join(".");
|
||||
const domainPlusTld2 = parseResult1.labels.slice(2).join(".");
|
||||
|
@ -163,15 +118,8 @@ export namespace Util {
|
|||
// no wildcard
|
||||
return d1 == d2 ? true : false;
|
||||
}
|
||||
} else if (
|
||||
parseResult1.type == ParseResultType.Reserved &&
|
||||
parseResult2.type == ParseResultType.Reserved
|
||||
) {
|
||||
if (
|
||||
parseResult1.hostname == "" &&
|
||||
parseResult1.hostname === parseResult2.hostname
|
||||
)
|
||||
return true;
|
||||
} else if (parseResult1.type == ParseResultType.Reserved && parseResult2.type == ParseResultType.Reserved) {
|
||||
if (parseResult1.hostname == "" && parseResult1.hostname === parseResult2.hostname) return true;
|
||||
else {
|
||||
// uncertain, fallback to exact hostname matching
|
||||
return d1 == d2 ? true : false;
|
||||
|
@ -229,10 +177,7 @@ export namespace Util {
|
|||
flags: number;
|
||||
};
|
||||
|
||||
export function generatePositionPacket(
|
||||
currentPos: PlayerPosition,
|
||||
newPos: PositionPacket
|
||||
): PositionPacket {
|
||||
export function generatePositionPacket(currentPos: PlayerPosition, newPos: PositionPacket): PositionPacket {
|
||||
const DEFAULT_RELATIVITY = 0x01; // relative to X-axis
|
||||
const newPosPacket = {
|
||||
x: newPos.x - currentPos.x * 2,
|
||||
|
@ -244,4 +189,8 @@ export namespace Util {
|
|||
};
|
||||
return newPosPacket;
|
||||
}
|
||||
|
||||
export type Handlable = {
|
||||
handled: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,10 +19,7 @@ import { EaglerSkins } from "../skins/EaglerSkins.js";
|
|||
import { BungeeUtil } from "../BungeeUtil.js";
|
||||
|
||||
export class PluginManager extends EventEmitter {
|
||||
public plugins: Map<
|
||||
string,
|
||||
{ exports: any; metadata: PluginLoaderTypes.PluginMetadataPathed }
|
||||
>;
|
||||
public plugins: Map<string, { exports: any; metadata: PluginLoaderTypes.PluginMetadataPathed }>;
|
||||
public proxy: Proxy;
|
||||
|
||||
public Logger: typeof Logger = Logger;
|
||||
|
@ -59,179 +56,67 @@ export class PluginManager extends EventEmitter {
|
|||
}
|
||||
pluginsString = pluginsString.substring(0, pluginsString.length - 1);
|
||||
this._logger.info(`Found ${pluginMeta.size} plugin(s): ${pluginsString}`);
|
||||
if(pluginMeta.size !== 0){
|
||||
if (pluginMeta.size !== 0) {
|
||||
this._logger.info(`Loading ${pluginMeta.size} plugin(s)...`);
|
||||
const successLoadCount = await this._loadPlugins(
|
||||
pluginMeta,
|
||||
this._getLoadOrder(pluginMeta)
|
||||
);
|
||||
const successLoadCount = await this._loadPlugins(pluginMeta, this._getLoadOrder(pluginMeta));
|
||||
this._logger.info(`Successfully loaded ${successLoadCount} plugin(s).`);
|
||||
}
|
||||
this.emit("pluginsFinishLoading", this);
|
||||
}
|
||||
|
||||
private async _findPlugins(
|
||||
dir: string
|
||||
): Promise<Map<string, PluginLoaderTypes.PluginMetadataPathed>> {
|
||||
private async _findPlugins(dir: string): Promise<Map<string, PluginLoaderTypes.PluginMetadataPathed>> {
|
||||
const ret: Map<string, PluginLoaderTypes.PluginMetadataPathed> = new Map();
|
||||
const lsRes = (await Promise.all(
|
||||
(await fs.readdir(dir))
|
||||
.filter((ent) => !ent.endsWith(".disabled"))
|
||||
.map(async (res) => [
|
||||
pathUtil.join(dir, res),
|
||||
await fs.stat(pathUtil.join(dir, res)),
|
||||
])
|
||||
)) as [string, Stats][];
|
||||
const lsRes = (await Promise.all((await fs.readdir(dir)).filter((ent) => !ent.endsWith(".disabled")).map(async (res) => [pathUtil.join(dir, res), await fs.stat(pathUtil.join(dir, res))]))) as [string, Stats][];
|
||||
for (const [path, details] of lsRes) {
|
||||
if (details.isFile()) {
|
||||
if (path.endsWith(".jar")) {
|
||||
this._logger.warn(`Non-EaglerProxy plugin found! (${path})`);
|
||||
this._logger.warn(
|
||||
`BungeeCord plugins are NOT supported! Only custom EaglerProxy plugins are allowed.`
|
||||
);
|
||||
this._logger.warn(`BungeeCord plugins are NOT supported! Only custom EaglerProxy plugins are allowed.`);
|
||||
} else if (path.endsWith(".zip")) {
|
||||
this._logger.warn(`.zip file found in plugin directory! (${path})`);
|
||||
this._logger.warn(
|
||||
`A .zip file was found in the plugins directory! Perhaps you forgot to unzip it?`
|
||||
);
|
||||
} else
|
||||
this._logger.debug(`Skipping file found in plugin folder: ${path}`);
|
||||
this._logger.warn(`A .zip file was found in the plugins directory! Perhaps you forgot to unzip it?`);
|
||||
} else this._logger.debug(`Skipping file found in plugin folder: ${path}`);
|
||||
} else {
|
||||
const metadataPath = pathUtil.resolve(
|
||||
pathUtil.join(path, "metadata.json")
|
||||
);
|
||||
const metadataPath = pathUtil.resolve(pathUtil.join(path, "metadata.json"));
|
||||
let metadata: PluginLoaderTypes.PluginMetadata;
|
||||
try {
|
||||
const file = await fs.readFile(metadataPath);
|
||||
metadata = JSON.parse(file.toString());
|
||||
// do some type checking
|
||||
if (typeof metadata.name != "string")
|
||||
throw new TypeError(
|
||||
"<metadata>.name is either null or not of a string type!"
|
||||
);
|
||||
if (typeof metadata.id != "string")
|
||||
throw new TypeError(
|
||||
"<metadata>.id is either null or not of a string type!"
|
||||
);
|
||||
if (/ /gm.test(metadata.id))
|
||||
throw new Error(`<metadata>.id contains whitespace!`);
|
||||
if (!semver.valid(metadata.version))
|
||||
throw new Error(
|
||||
"<metadata>.version is either null, not a string, or is not a valid SemVer!"
|
||||
);
|
||||
if (typeof metadata.entry_point != "string")
|
||||
throw new TypeError(
|
||||
"<metadata>.entry_point is either null or not a string!"
|
||||
);
|
||||
if (!metadata.entry_point.endsWith(".js"))
|
||||
throw new Error(
|
||||
`<metadata>.entry_point (${metadata.entry_point}) references a non-JavaScript file!`
|
||||
);
|
||||
if (
|
||||
!(await Util.fsExists(pathUtil.resolve(path, metadata.entry_point)))
|
||||
)
|
||||
throw new Error(
|
||||
`<metadata>.entry_point (${metadata.entry_point}) references a non-existent file!`
|
||||
);
|
||||
if (metadata.requirements instanceof Array == false)
|
||||
throw new TypeError(
|
||||
"<metadata>.requirements is either null or not an array!"
|
||||
);
|
||||
if (typeof metadata.name != "string") throw new TypeError("<metadata>.name is either null or not of a string type!");
|
||||
if (typeof metadata.id != "string") throw new TypeError("<metadata>.id is either null or not of a string type!");
|
||||
if (/ /gm.test(metadata.id)) throw new Error(`<metadata>.id contains whitespace!`);
|
||||
if (!semver.valid(metadata.version)) throw new Error("<metadata>.version is either null, not a string, or is not a valid SemVer!");
|
||||
if (typeof metadata.entry_point != "string") throw new TypeError("<metadata>.entry_point is either null or not a string!");
|
||||
if (!metadata.entry_point.endsWith(".js")) throw new Error(`<metadata>.entry_point (${metadata.entry_point}) references a non-JavaScript file!`);
|
||||
if (!(await Util.fsExists(pathUtil.resolve(path, metadata.entry_point)))) throw new Error(`<metadata>.entry_point (${metadata.entry_point}) references a non-existent file!`);
|
||||
if (metadata.requirements instanceof Array == false) throw new TypeError("<metadata>.requirements is either null or not an array!");
|
||||
for (const requirement of metadata.requirements as PluginLoaderTypes.PluginMetadata["requirements"]) {
|
||||
if (typeof requirement != "object" || requirement == null)
|
||||
throw new TypeError(
|
||||
`<metadata>.requirements[${(
|
||||
metadata.requirements as any
|
||||
).indexOf(requirement)}] is either null or not an object!`
|
||||
);
|
||||
if (typeof requirement.id != "string")
|
||||
throw new TypeError(
|
||||
`<metadata>.requirements[${(
|
||||
metadata.requirements as any
|
||||
).indexOf(requirement)}].id is either null or not a string!`
|
||||
);
|
||||
if (/ /gm.test(requirement.id))
|
||||
throw new TypeError(
|
||||
`<metadata>.requirements[${(
|
||||
metadata.requirements as any
|
||||
).indexOf(requirement)}].id contains whitespace!`
|
||||
);
|
||||
if (
|
||||
semver.validRange(requirement.version) == null &&
|
||||
requirement.version != "any"
|
||||
)
|
||||
throw new TypeError(
|
||||
`<metadata>.requirements[${(
|
||||
metadata.requirements as any
|
||||
).indexOf(
|
||||
requirement
|
||||
)}].version is either null or not a valid SemVer!`
|
||||
);
|
||||
if (typeof requirement != "object" || requirement == null) throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}] is either null or not an object!`);
|
||||
if (typeof requirement.id != "string") throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}].id is either null or not a string!`);
|
||||
if (/ /gm.test(requirement.id)) throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}].id contains whitespace!`);
|
||||
if (semver.validRange(requirement.version) == null && requirement.version != "any")
|
||||
throw new TypeError(`<metadata>.requirements[${(metadata.requirements as any).indexOf(requirement)}].version is either null or not a valid SemVer!`);
|
||||
}
|
||||
if (metadata.load_after instanceof Array == false)
|
||||
throw new TypeError(
|
||||
"<metadata>.load_after is either null or not an array!"
|
||||
);
|
||||
if (metadata.load_after instanceof Array == false) throw new TypeError("<metadata>.load_after is either null or not an array!");
|
||||
for (const loadReq of metadata.load_after as string[]) {
|
||||
if (typeof loadReq != "string")
|
||||
throw new TypeError(
|
||||
`<metadata>.load_after[${(metadata.load_after as any).indexOf(
|
||||
loadReq
|
||||
)}] is either null, or not a valid ID!`
|
||||
);
|
||||
if (/ /gm.test(loadReq))
|
||||
throw new TypeError(
|
||||
`<metadata>.load_after[${(metadata.load_after as any).indexOf(
|
||||
loadReq
|
||||
)}] contains whitespace!`
|
||||
);
|
||||
if (typeof loadReq != "string") throw new TypeError(`<metadata>.load_after[${(metadata.load_after as any).indexOf(loadReq)}] is either null, or not a valid ID!`);
|
||||
if (/ /gm.test(loadReq)) throw new TypeError(`<metadata>.load_after[${(metadata.load_after as any).indexOf(loadReq)}] contains whitespace!`);
|
||||
}
|
||||
if (metadata.incompatibilities instanceof Array == false)
|
||||
throw new TypeError(
|
||||
"<metadata>.incompatibilities is either null or not an array!"
|
||||
);
|
||||
if (metadata.incompatibilities instanceof Array == false) throw new TypeError("<metadata>.incompatibilities is either null or not an array!");
|
||||
for (const incompatibility of metadata.incompatibilities as PluginLoaderTypes.PluginMetadata["requirements"]) {
|
||||
if (typeof incompatibility != "object" || incompatibility == null)
|
||||
throw new TypeError(
|
||||
`<metadata>.incompatibilities[${(
|
||||
metadata.load_after as any
|
||||
).indexOf(incompatibility)}] is either null or not an object!`
|
||||
);
|
||||
if (typeof incompatibility.id != "string")
|
||||
throw new TypeError(
|
||||
`<metadata>.incompatibilities[${(
|
||||
metadata.load_after as any
|
||||
).indexOf(incompatibility)}].id is either null or not a string!`
|
||||
);
|
||||
if (/ /gm.test(incompatibility.id))
|
||||
throw new TypeError(
|
||||
`<metadata>.incompatibilities[${(
|
||||
metadata.load_after as any
|
||||
).indexOf(incompatibility)}].id contains whitespace!`
|
||||
);
|
||||
if (semver.validRange(incompatibility.version) == null)
|
||||
throw new TypeError(
|
||||
`<metadata>.incompatibilities[${(
|
||||
metadata.load_after as any
|
||||
).indexOf(
|
||||
incompatibility
|
||||
)}].version is either null or not a valid SemVer!`
|
||||
);
|
||||
if (typeof incompatibility != "object" || incompatibility == null) throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}] is either null or not an object!`);
|
||||
if (typeof incompatibility.id != "string") throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}].id is either null or not a string!`);
|
||||
if (/ /gm.test(incompatibility.id)) throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}].id contains whitespace!`);
|
||||
if (semver.validRange(incompatibility.version) == null) throw new TypeError(`<metadata>.incompatibilities[${(metadata.load_after as any).indexOf(incompatibility)}].version is either null or not a valid SemVer!`);
|
||||
}
|
||||
if (ret.has(metadata.id))
|
||||
throw new Error(
|
||||
`Duplicate plugin ID detected: ${metadata.id}. Are there duplicate plugins in the plugin folder?`
|
||||
);
|
||||
if (ret.has(metadata.id)) throw new Error(`Duplicate plugin ID detected: ${metadata.id}. Are there duplicate plugins in the plugin folder?`);
|
||||
ret.set(metadata.id, {
|
||||
path: pathUtil.resolve(path),
|
||||
...metadata,
|
||||
});
|
||||
} catch (err) {
|
||||
this._logger.warn(
|
||||
`Failed to load plugin metadata file at ${metadataPath}: ${
|
||||
err.stack ?? err
|
||||
}`
|
||||
);
|
||||
this._logger.warn(`Failed to load plugin metadata file at ${metadataPath}: ${err.stack ?? err}`);
|
||||
this._logger.warn("This plugin will skip loading due to an error.");
|
||||
}
|
||||
}
|
||||
|
@ -239,27 +124,16 @@ export class PluginManager extends EventEmitter {
|
|||
return ret;
|
||||
}
|
||||
|
||||
private async _validatePluginList(
|
||||
plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>
|
||||
) {
|
||||
private async _validatePluginList(plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>) {
|
||||
for (const [id, plugin] of plugins) {
|
||||
for (const req of plugin.requirements) {
|
||||
if (
|
||||
!plugins.has(req.id) &&
|
||||
req.id != "eaglerproxy" &&
|
||||
!req.id.startsWith("module:")
|
||||
) {
|
||||
this._logger.fatal(
|
||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires plugin ${req.id}@${req.version}, but it is not found!`
|
||||
);
|
||||
if (!plugins.has(req.id) && req.id != "eaglerproxy" && !req.id.startsWith("module:")) {
|
||||
this._logger.fatal(`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires plugin ${req.id}@${req.version}, but it is not found!`);
|
||||
this._logger.fatal("Loading has halted due to missing dependencies.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (req.id == "eaglerproxy") {
|
||||
if (
|
||||
!semver.satisfies(PROXY_VERSION, req.version) &&
|
||||
req.version != "any"
|
||||
) {
|
||||
if (!semver.satisfies(PROXY_VERSION, req.version) && req.version != "any") {
|
||||
this._logger.fatal(
|
||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires a proxy version that satisfies the SemVer requirement ${req.version}, but the proxy version is ${PROXY_VERSION} and does not satisfy the SemVer requirement!`
|
||||
);
|
||||
|
@ -272,32 +146,19 @@ export class PluginManager extends EventEmitter {
|
|||
await import(moduleName);
|
||||
} catch (err) {
|
||||
if (err.code == "ERR_MODULE_NOT_FOUND") {
|
||||
this._logger.fatal(`Plugin ${plugin.name}@${plugin.version} requires NPM module ${moduleName}${req.version == "any" ? "" : `@${req.version}`} to be installed, but it is not found!`);
|
||||
this._logger.fatal(
|
||||
`Plugin ${plugin.name}@${
|
||||
plugin.version
|
||||
} requires NPM module ${moduleName}${
|
||||
req.version == "any" ? "" : `@${req.version}`
|
||||
} to be installed, but it is not found!`
|
||||
);
|
||||
this._logger.fatal(
|
||||
`Please install this missing package by running "npm install ${moduleName}${
|
||||
req.version == "any" ? "" : `@${req.version}`
|
||||
}". If you're using yarn, run "yarn add ${moduleName}${
|
||||
`Please install this missing package by running "npm install ${moduleName}${req.version == "any" ? "" : `@${req.version}`}". If you're using yarn, run "yarn add ${moduleName}${
|
||||
req.version == "any" ? "" : `@${req.version}`
|
||||
}" instead.`
|
||||
);
|
||||
this._logger.fatal(
|
||||
"Loading has halted due to dependency issues."
|
||||
);
|
||||
this._logger.fatal("Loading has halted due to dependency issues.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let dep = plugins.get(req.id);
|
||||
if (
|
||||
!semver.satisfies(dep.version, req.version) &&
|
||||
req.version != "any"
|
||||
) {
|
||||
if (!semver.satisfies(dep.version, req.version) && req.version != "any") {
|
||||
this._logger.fatal(
|
||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} requires a version of plugin ${dep.name} that satisfies the SemVer requirement ${req.version}, but the plugin ${dep.name}'s version is ${dep.version} and does not satisfy the SemVer requirement!`
|
||||
);
|
||||
|
@ -313,19 +174,13 @@ export class PluginManager extends EventEmitter {
|
|||
this._logger.fatal(
|
||||
`Error whilst loading plugins: Plugin incompatibility found! Plugin ${plugin.name}@${plugin.version} is incompatible with ${plugin_incomp.name}@${plugin_incomp.version} as it satisfies the SemVer requirement of ${incomp.version}!`
|
||||
);
|
||||
this._logger.fatal(
|
||||
"Loading has halted due to plugin incompatibility issues."
|
||||
);
|
||||
this._logger.fatal("Loading has halted due to plugin incompatibility issues.");
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (incomp.id == "eaglerproxy") {
|
||||
if (semver.satisfies(PROXY_VERSION, incomp.version)) {
|
||||
this._logger.fatal(
|
||||
`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} is incompatible with proxy version ${PROXY_VERSION} as it satisfies the SemVer requirement of ${incomp.version}!`
|
||||
);
|
||||
this._logger.fatal(
|
||||
"Loading has halted due to plugin incompatibility issues."
|
||||
);
|
||||
this._logger.fatal(`Error whilst loading plugins: Plugin ${plugin.name}@${plugin.version} is incompatible with proxy version ${PROXY_VERSION} as it satisfies the SemVer requirement of ${incomp.version}!`);
|
||||
this._logger.fatal("Loading has halted due to plugin incompatibility issues.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -333,9 +188,7 @@ export class PluginManager extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
private _getLoadOrder(
|
||||
plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>
|
||||
): PluginLoaderTypes.PluginLoadOrder {
|
||||
private _getLoadOrder(plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>): PluginLoaderTypes.PluginLoadOrder {
|
||||
let order = [],
|
||||
lastPlugin: any;
|
||||
plugins.forEach((v) => order.push(v.id));
|
||||
|
@ -362,21 +215,12 @@ export class PluginManager extends EventEmitter {
|
|||
return order;
|
||||
}
|
||||
|
||||
private async _loadPlugins(
|
||||
plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>,
|
||||
order: PluginLoaderTypes.PluginLoadOrder
|
||||
): Promise<number> {
|
||||
private async _loadPlugins(plugins: Map<string, PluginLoaderTypes.PluginMetadataPathed>, order: PluginLoaderTypes.PluginLoadOrder): Promise<number> {
|
||||
let successCount = 0;
|
||||
for (const id of order) {
|
||||
let pluginMeta = plugins.get(id);
|
||||
try {
|
||||
const imp = await import(
|
||||
process.platform == "win32"
|
||||
? pathToFileURL(
|
||||
pathUtil.join(pluginMeta.path, pluginMeta.entry_point)
|
||||
).toString()
|
||||
: pathUtil.join(pluginMeta.path, pluginMeta.entry_point)
|
||||
);
|
||||
const imp = await import(process.platform == "win32" ? pathToFileURL(pathUtil.join(pluginMeta.path, pluginMeta.entry_point)).toString() : pathUtil.join(pluginMeta.path, pluginMeta.entry_point));
|
||||
this.plugins.set(pluginMeta.id, {
|
||||
exports: imp,
|
||||
metadata: pluginMeta,
|
||||
|
@ -384,11 +228,7 @@ export class PluginManager extends EventEmitter {
|
|||
successCount++;
|
||||
this.emit("pluginLoad", pluginMeta.id, imp);
|
||||
} catch (err) {
|
||||
this._logger.warn(
|
||||
`Failed to load plugin entry point for plugin (${
|
||||
pluginMeta.name
|
||||
}) at ${pluginMeta.path}: ${err.stack ?? err}`
|
||||
);
|
||||
this._logger.warn(`Failed to load plugin entry point for plugin (${pluginMeta.name}) at ${pluginMeta.path}: ${err.stack ?? err}`);
|
||||
this._logger.warn("This plugin will skip loading due to an error.");
|
||||
}
|
||||
return successCount;
|
||||
|
@ -403,18 +243,9 @@ interface PluginManagerEvents {
|
|||
}
|
||||
|
||||
export declare interface PluginManager {
|
||||
on<U extends keyof PluginManagerEvents>(
|
||||
event: U,
|
||||
listener: PluginManagerEvents[U]
|
||||
): this;
|
||||
on<U extends keyof PluginManagerEvents>(event: U, listener: PluginManagerEvents[U]): this;
|
||||
|
||||
emit<U extends keyof PluginManagerEvents>(
|
||||
event: U,
|
||||
...args: Parameters<PluginManagerEvents[U]>
|
||||
): boolean;
|
||||
emit<U extends keyof PluginManagerEvents>(event: U, ...args: Parameters<PluginManagerEvents[U]>): boolean;
|
||||
|
||||
once<U extends keyof PluginManagerEvents>(
|
||||
event: U,
|
||||
listener: PluginManagerEvents[U]
|
||||
): this;
|
||||
once<U extends keyof PluginManagerEvents>(event: U, listener: PluginManagerEvents[U]): this;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user