Support latest Eaglercraft and move to TheAltening

EasyMC shut down :(
This commit is contained in:
ayunami2000 2024-12-04 23:48:47 -05:00
parent 1687d30417
commit 6fba77dbfd
9 changed files with 510 additions and 489 deletions

844
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,15 +16,15 @@
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"chalk": "5.2.0", "chalk": "5.2.0",
"dotenv": "16.0.3", "dotenv": "16.0.3",
"minecraft-protocol": "^1.26.5", "minecraft-protocol": "^1.51.0",
"node-fetch": "3.3.1", "node-fetch": "3.3.1",
"parse-domain": "7.0.1", "parse-domain": "7.0.1",
"prismarine-auth": "^2.4.1", "prismarine-auth": "^2.5.0",
"prismarine-block": "1.16.3", "prismarine-block": "1.16.3",
"prismarine-chunk": "1.33.0", "prismarine-chunk": "1.33.0",
"prismarine-registry": "1.6.0", "prismarine-registry": "1.6.0",
"semver": "^7.6.0", "semver": "^7.6.3",
"sharp": "^0.33.2", "sharp": "^0.33.5",
"ws": "8.12.0" "ws": "8.12.0"
}, },
"type": "module" "type": "module"

View File

@ -1,31 +0,0 @@
import mc from "minecraft-protocol";
import { Enums } from "../../proxy/Enums.js";
export async function getTokenProfileEasyMc(token: string): Promise<object> {
const fetchOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token,
}),
};
const res = await fetch("https://api.easymc.io/v1/token/redeem", fetchOptions);
const resJson = await res.json();
if (resJson.error) throw new Error(Enums.ChatColor.RED + `${resJson.error}`);
if (!resJson) throw new Error(Enums.ChatColor.RED + "EasyMC replied with an empty response!?");
if (resJson.session?.length !== 43 || resJson.mcName?.length < 3 || resJson.uuid?.length !== 36) throw new Error(Enums.ChatColor.RED + "Invalid response from EasyMC received!");
return {
auth: "mojang",
sessionServer: "https://sessionserver.easymc.io",
username: resJson.mcName,
haveCredentials: true,
session: {
accessToken: resJson.session,
selectedProfile: {
name: resJson.mcName,
id: resJson.uuid,
},
},
};
}

View File

@ -0,0 +1,26 @@
import mc from "minecraft-protocol";
import { Enums } from "../../proxy/Enums.js";
export async function getTokenProfileTheAltening(token: string): Promise<object> {
const fetchOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: token,
password: "anything",
}),
};
const res = await fetch("http://authserver.thealtening.com/authenticate", fetchOptions);
const resJson = await res.json();
if (resJson.error) throw new Error(Enums.ChatColor.RED + `${resJson.error}`);
if (!resJson) throw new Error(Enums.ChatColor.RED + "TheAltening replied with an empty response!?");
if (resJson.selectedProfile?.name?.length < 3) throw new Error(Enums.ChatColor.RED + "Invalid response from TheAltening received!");
return {
auth: "mojang",
sessionServer: "http://sessionserver.thealtening.com",
username: resJson.selectedProfile.name,
haveCredentials: true,
session: resJson,
};
}

View File

@ -127,7 +127,7 @@ export function helpCommand(sender: Player) {
}, },
extra: [ extra: [
{ {
text: " - Switch between servers on-the-fly. Switching to servers in online mode requires logging in via online mode or EasyMC!", text: " - Switch between servers on-the-fly. Switching to servers in online mode requires logging in via online mode or TheAltening!",
color: "aqua", color: "aqua",
}, },
], ],
@ -252,18 +252,18 @@ export async function switchServer(cmd: string, sender: Player) {
if (connectionType == ConnectType.ONLINE) { if (connectionType == ConnectType.ONLINE) {
if ((sender as any)._onlineSession == null) { if ((sender as any)._onlineSession == null) {
sendPluginChatMessage(sender, { sendPluginChatMessage(sender, {
text: `You either connected to this proxy under offline mode, or your online/EasyMC session has timed out and has become invalid.`, text: `You either connected to this proxy under offline mode, or your online/TheAltening session has timed out and has become invalid.`,
color: "red", color: "red",
}); });
return sendPluginChatMessage(sender, { return sendPluginChatMessage(sender, {
text: `To switch to online servers, please reconnect and log-in through online/EasyMC mode.`, text: `To switch to online servers, please reconnect and log-in through online/TheAltening mode.`,
color: "red", color: "red",
}); });
} else { } else {
const savedAuth = (sender as any)._onlineSession; const savedAuth = (sender as any)._onlineSession;
sendPluginChatMessage(sender, { sendPluginChatMessage(sender, {
text: `(joining server under ${savedAuth.username}/your ${ text: `(joining server under ${savedAuth.username}/your ${
savedAuth.isEasyMC ? "EasyMC" : "Minecraft" savedAuth.isTheAltening ? "TheAltening" : "Minecraft"
} account's username)`, } account's username)`,
color: "aqua", color: "aqua",
}); });

View File

@ -47,5 +47,5 @@ export enum ChatColor {
export enum ConnectType { export enum ConnectType {
ONLINE = "ONLINE", ONLINE = "ONLINE",
OFFLINE = "OFFLINE", OFFLINE = "OFFLINE",
EASYMC = "EASYMC", THEALTENING = "THEALTENING",
} }

View File

@ -8,7 +8,7 @@ import { ClientState, ConnectionState } from "./types.js";
import { auth, ServerDeviceCodeResponse } from "./auth.js"; import { auth, ServerDeviceCodeResponse } from "./auth.js";
import { config } from "./config.js"; import { config } from "./config.js";
import { handleCommand } from "./commands.js"; import { handleCommand } from "./commands.js";
import { getTokenProfileEasyMc } from "./auth_easymc.js"; import { getTokenProfileTheAltening } from "./auth_thealtening.js";
const { Vec3 } = vec3 as any; const { Vec3 } = vec3 as any;
const Enums = PLUGIN_MANAGER.Enums; const Enums = PLUGIN_MANAGER.Enums;
@ -163,7 +163,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_THEALTENING" | "AUTH" | "SERVER", uri?: string, code?: string) {
switch (newState) { switch (newState) {
case "CONNECTION_TYPE": case "CONNECTION_TYPE":
client.write("playerlist_header", { client.write("playerlist_header", {
@ -171,17 +171,17 @@ export function updateState(client: Client, newState: "CONNECTION_TYPE" | "AUTH_
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
}), }),
footer: JSON.stringify({ footer: JSON.stringify({
text: `${Enums.ChatColor.RED}Choose the connection type: 1 = online, 2 = offline, 3 = EasyMC.`, text: `${Enums.ChatColor.RED}Choose the connection type: 1 = online, 2 = offline, 3 = TheAltening.`,
}), }),
}); });
break; break;
case "AUTH_EASYMC": case "AUTH_THEALTENING":
client.write("playerlist_header", { client.write("playerlist_header", {
header: JSON.stringify({ header: JSON.stringify({
text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `, text: ` ${Enums.ChatColor.GOLD}EaglerProxy Authentication Server `,
}), }),
footer: JSON.stringify({ footer: JSON.stringify({
text: `${Enums.ChatColor.RED}easymc.io/get${Enums.ChatColor.GOLD} | ${Enums.ChatColor.RED}/login <alt_token>`, text: `${Enums.ChatColor.RED}panel.thealtening.com/#generator${Enums.ChatColor.GOLD} | ${Enums.ChatColor.RED}/login <alt_token>`,
}), }),
}); });
break; break;
@ -301,7 +301,7 @@ export async function onConnect(client: ClientState) {
color: "gold", color: "gold",
extra: [ extra: [
{ {
text: "Connect to an online server via EasyMC account pool (no Minecraft account needed)", text: "Connect to an online server via TheAltening account pool (no Minecraft account needed)",
color: "white", color: "white",
}, },
], ],
@ -314,7 +314,7 @@ export async function onConnect(client: ClientState) {
value: "$3", 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 = TheAltening), either by clicking or manually typing out the option's number on the list.", "green");
updateState(client.gameClient, "CONNECTION_TYPE"); updateState(client.gameClient, "CONNECTION_TYPE");
let chosenOption: ConnectType | null = null; let chosenOption: ConnectType | null = null;
@ -331,7 +331,7 @@ export async function onConnect(client: ClientState) {
chosenOption = ConnectType.OFFLINE; chosenOption = ConnectType.OFFLINE;
break; break;
case "3": case "3":
chosenOption = ConnectType.EASYMC; chosenOption = ConnectType.THEALTENING;
break; break;
} }
if (chosenOption != null) { if (chosenOption != null) {
@ -479,19 +479,19 @@ export async function onConnect(client: ClientState) {
); );
} }
} }
} else if (chosenOption == ConnectType.EASYMC) { } else if (chosenOption == ConnectType.THEALTENING) {
const EASYMC_GET_TOKEN_URL = "easymc.io/get"; const THEALTENING_GET_TOKEN_URL = "panel.thealtening.com/#generator";
client.state = ConnectionState.AUTH; client.state = ConnectionState.AUTH;
client.lastStatusUpdate = Date.now(); client.lastStatusUpdate = Date.now();
updateState(client.gameClient, "AUTH_EASYMC"); updateState(client.gameClient, "AUTH_THEALTENING");
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 TheAltening's account pool. Please note that accounts and shared, and may be banned from whatever server you are attempting to join.`);
sendChatComponent(client.gameClient, { sendChatComponent(client.gameClient, {
text: "Please generate an alt token at ", text: "Please log in and generate an alt token at ",
color: "white", color: "white",
extra: [ extra: [
{ {
text: EASYMC_GET_TOKEN_URL, text: THEALTENING_GET_TOKEN_URL,
color: "gold", color: "gold",
hoverEvent: { hoverEvent: {
action: "show_text", action: "show_text",
@ -499,7 +499,7 @@ export async function onConnect(client: ClientState) {
}, },
clickEvent: { clickEvent: {
action: "open_url", action: "open_url",
value: `https://${EASYMC_GET_TOKEN_URL}`, value: `https://${THEALTENING_GET_TOKEN_URL}`,
}, },
}, },
{ {
@ -554,7 +554,7 @@ export async function onConnect(client: ClientState) {
}); });
} else { } else {
const token = splitResponse[0]; const token = splitResponse[0];
if (token.length != 20) { if (!token.endsWith("@alt.com")) {
sendChatComponent(client.gameClient, { sendChatComponent(client.gameClient, {
text: "Please provide a valid token (you can get one ", text: "Please provide a valid token (you can get one ",
color: "red", color: "red",
@ -568,7 +568,7 @@ export async function onConnect(client: ClientState) {
}, },
clickEvent: { clickEvent: {
action: "open_url", action: "open_url",
value: `https://${EASYMC_GET_TOKEN_URL}`, value: `https://${THEALTENING_GET_TOKEN_URL}`,
}, },
}, },
{ {
@ -596,12 +596,12 @@ export async function onConnect(client: ClientState) {
} else { } else {
sendCustomMessage(client.gameClient, "Validating alt token...", "gray"); sendCustomMessage(client.gameClient, "Validating alt token...", "gray");
try { try {
appendOptions = await getTokenProfileEasyMc(token); appendOptions = await getTokenProfileTheAltening(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; break;
} catch (err) { } catch (err) {
sendChatComponent(client.gameClient, { sendChatComponent(client.gameClient, {
text: `EasyMC's servers replied with an error (${err.message}), please try again! `, text: `TheAltening's servers replied with an error (${err.message}), please try again! `,
color: "red", color: "red",
extra: [ extra: [
{ {
@ -652,7 +652,7 @@ export async function onConnect(client: ClientState) {
} }
try { try {
sendChatComponent(client.gameClient, { sendChatComponent(client.gameClient, {
text: `Joining server under ${appendOptions.username}/EasyMC account username! Run `, text: `Joining server under ${appendOptions.username}/TheAltening account username! Run `,
color: "aqua", color: "aqua",
extra: [ extra: [
{ {
@ -673,7 +673,7 @@ 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!`); logger.info(`Player ${client.gameClient.username} is attempting to connect to ${host}:${port} under their TheAltening alt token's username (${appendOptions.username}) using TheAltening mode!`);
const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username); const player = PLUGIN_MANAGER.proxy.players.get(client.gameClient.username);
player.on("vanillaPacket", (packet, origin) => { 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) {
@ -683,7 +683,7 @@ export async function onConnect(client: ClientState) {
}); });
(player as any)._onlineSession = { (player as any)._onlineSession = {
...appendOptions, ...appendOptions,
isEasyMC: true, isTheAltening: true,
}; };
await player.switchServers({ await player.switchServers({

View File

@ -189,7 +189,7 @@ export class Proxy extends EventEmitter {
} }
}, this.LOGIN_TIMEOUT); }, this.LOGIN_TIMEOUT);
try { try {
if (firstPacket.toString() === "Accept: MOTD") { if (firstPacket.toString().toLowerCase() === "accept: motd") {
if (!this.ratelimit.motd.consume(req.socket.remoteAddress).success) { if (!this.ratelimit.motd.consume(req.socket.remoteAddress).success) {
return ws.close(); return ws.close();
} }

View File

@ -15,22 +15,15 @@ export default class CSLoginPacket implements Packet {
version: string; version: string;
username: string; username: string;
private _getMagicSeq(): Buffer {
return Buffer.concat(
[
[0x02, 0x00, 0x02, 0x00, 0x02, 0x00],
[this.networkVersion],
[0x00, 0x01, 0x00],
[this.gameVersion],
].map((arr) => Buffer.from(arr))
);
}
public serialize() { public serialize() {
return Buffer.concat( return Buffer.concat(
[ [
[Enums.PacketId.CSLoginPacket], [Enums.PacketId.CSLoginPacket],
this._getMagicSeq(), [0x02],
MineProtocol.writeShort(0x01),
MineProtocol.writeShort(this.networkVersion),
MineProtocol.writeShort(0x01),
MineProtocol.writeShort(this.gameVersion),
MineProtocol.writeString(this.brand), MineProtocol.writeString(this.brand),
MineProtocol.writeString(this.version), MineProtocol.writeString(this.version),
[0x00], [0x00],
@ -41,8 +34,19 @@ export default class CSLoginPacket implements Packet {
public deserialize(packet: Buffer) { public deserialize(packet: Buffer) {
if (packet[0] != this.packetId) if (packet[0] != this.packetId)
throw TypeError("Invalid packet ID detected!"); throw TypeError("Invalid packet ID detected!");
packet = packet.subarray(1 + this._getMagicSeq().length); packet = packet.subarray(2);
const brand = MineProtocol.readString(packet), let fard = MineProtocol.readShort(packet);
// Math.min used in feeble attempt at anti DoS
let fv = Math.min(8, fard.value);
for (let i = 0; i < fv; i++) {
fard = MineProtocol.readShort(fard.newBuffer);
}
fard = MineProtocol.readShort(fard.newBuffer);
fv = Math.min(8, fard.value);
for (let i = 0; i < fv; i++) {
fard = MineProtocol.readShort(fard.newBuffer);
}
const brand = MineProtocol.readString(fard.newBuffer),
version = MineProtocol.readString(brand.newBuffer), version = MineProtocol.readString(brand.newBuffer),
username = MineProtocol.readString(version.newBuffer, 1); username = MineProtocol.readString(version.newBuffer, 1);
this.brand = brand.value; this.brand = brand.value;
@ -50,4 +54,4 @@ export default class CSLoginPacket implements Packet {
this.username = username.value; this.username = username.value;
return this; return this;
} }
} }