diff --git a/.gitignore b/.gitignore index 93c3403..00c3bc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -insecureSocks \ No newline at end of file +insecureSocks +build \ No newline at end of file diff --git a/classes.js b/classes.js deleted file mode 100644 index 0bda634..0000000 --- a/classes.js +++ /dev/null @@ -1,2 +0,0 @@ -export class ProxiedPlayer { -} diff --git a/config.js b/config.js deleted file mode 100644 index 00ebabd..0000000 --- a/config.js +++ /dev/null @@ -1,19 +0,0 @@ -export const config = { - name: "BasedProxy", - port: 80, - maxPlayers: 20, - motd: { - iconURL: null, - l1: "hi", - l2: "lol" - }, - server: { - host: "127.0.0.1", - port: 25565 - }, - security: { - enabled: false, - key: null, - cert: null - } -}; diff --git a/eaglerPacketDef.js b/eaglerPacketDef.js deleted file mode 100644 index 4ecf67f..0000000 --- a/eaglerPacketDef.js +++ /dev/null @@ -1,22 +0,0 @@ -export var EaglerPacketId; -(function (EaglerPacketId) { - EaglerPacketId[EaglerPacketId["IDENTIFY_CLIENT"] = 1] = "IDENTIFY_CLIENT"; - EaglerPacketId[EaglerPacketId["IDENTIFY_SERVER"] = 2] = "IDENTIFY_SERVER"; - EaglerPacketId[EaglerPacketId["LOGIN"] = 4] = "LOGIN"; - EaglerPacketId[EaglerPacketId["LOGIN_ACK"] = 5] = "LOGIN_ACK"; - EaglerPacketId[EaglerPacketId["SKIN"] = 7] = "SKIN"; - EaglerPacketId[EaglerPacketId["C_READY"] = 8] = "C_READY"; - EaglerPacketId[EaglerPacketId["COMPLETE_HANDSHAKE"] = 9] = "COMPLETE_HANDSHAKE"; - EaglerPacketId[EaglerPacketId["DISCONNECT"] = 255] = "DISCONNECT"; -})(EaglerPacketId || (EaglerPacketId = {})); -export var DisconnectReason; -(function (DisconnectReason) { - DisconnectReason[DisconnectReason["UNEXPECTED_PACKET"] = 1] = "UNEXPECTED_PACKET"; - DisconnectReason[DisconnectReason["DUPLICATE_USERNAME"] = 2] = "DUPLICATE_USERNAME"; - DisconnectReason[DisconnectReason["BAD_USERNAME"] = 3] = "BAD_USERNAME"; - DisconnectReason[DisconnectReason["SERVER_DISCONNECT"] = 4] = "SERVER_DISCONNECT"; - DisconnectReason[DisconnectReason["CUSTOM"] = 8] = "CUSTOM"; -})(DisconnectReason || (DisconnectReason = {})); -export const MAGIC_BUILTIN_SKIN_BYTES = [0x00, 0x05, 0x01, 0x00, 0x00, 0x00]; -export const MAGIC_ENDING_IDENTIFYS_BYTES = [0x00, 0x00, 0x00]; -// Afterwards, forward 0x01 (Join Game, Clientbound) and everything after that diff --git a/index.js b/index.js deleted file mode 100644 index 1a90cd9..0000000 --- a/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import { readFileSync } from "fs"; -import * as https from "https"; -import { WebSocketServer } from "ws"; -import { ProxiedPlayer } from "./classes.js"; -import { config } from "./config.js"; -import { handlePacket } from "./listener.js"; -import { Logger } from "./logger.js"; -import { BRANDING, NETWORK_VERSION, VERSION } from "./meta.js"; -import { State } from "./types.js"; -import { genUUID } from "./utils.js"; -const logger = new Logger("EagXProxy"); -const connectionLogger = new Logger("ConnectionHandler"); -global.PROXY = { - brand: BRANDING, - version: VERSION, - MOTDVersion: NETWORK_VERSION, - serverName: config.name, - secure: false, - proxyUUID: genUUID(config.name), - MOTD: { - icon: null, - motd: [config.motd.l1, config.motd.l2] - }, - playerStats: { - max: config.maxPlayers, - onlineCount: 0 - }, - wsServer: null, - players: new Map(), - logger: logger, - config: config -}; -PROXY.playerStats.onlineCount = PROXY.players.size; -let server; -if (PROXY.config.security.enabled) { - logger.info(`Starting SECURE WebSocket proxy on port ${config.port}...`); - if (process.env.REPL_SLUG) { - logger.warn("You appear to be running the proxy on Repl.it with encryption enabled. Please note that Repl.it by default provides encryption, and enabling encryption may or may not prevent you from connecting to the server."); - } - server = new WebSocketServer({ - server: https.createServer({ - key: readFileSync(config.security.key), - cert: readFileSync(config.security.cert) - }).listen(config.port) - }); -} -else { - logger.info(`Starting INSECURE WebSocket proxy on port ${config.port}...`); - server = new WebSocketServer({ - port: config.port - }); -} -PROXY.wsServer = server; -server.addListener('connection', c => { - connectionLogger.debug(`[CONNECTION] New inbound WebSocket connection from [/${c._socket.remoteAddress}:${c._socket.remotePort}]. (${c._socket.remotePort} -> ${config.port})`); - const plr = new ProxiedPlayer(); - plr.ws = c; - plr.ip = c._socket.remoteAddress; - plr.remotePort = c._socket.remotePort; - plr.state = State.PRE_HANDSHAKE; - c.on('message', msg => { - handlePacket(msg, plr); - }); -}); -server.on('listening', () => { - logger.info(`Successfully started${config.security.enabled ? " [secure]" : ""} WebSocket proxy on port ${config.port}!`); -}); diff --git a/listener.js b/listener.js deleted file mode 100644 index 7289525..0000000 --- a/listener.js +++ /dev/null @@ -1,26 +0,0 @@ -import { Logger } from "./logger.js"; -import { handleMotd } from "./motd.js"; -import { State } from "./types.js"; -import { doHandshake } from "./utils.js"; -const logger = new Logger("PacketHandler"); -export function handlePacket(packet, client) { - if (client.state == State.PRE_HANDSHAKE) { - if (packet.toString() === "Accept: MOTD") { - handleMotd(client); - } - else if (!client._handled) { - ; - client._handled = true; - doHandshake(client, packet); - } - } - else if (client.state == State.POST_HANDSHAKE) { - if (!client.remoteConnection || client.remoteConnection.socket.closed) { - logger.warn(`Player ${client.username} is marked as post handshake, but is disconnected from the game server? Disconnecting due to illegal state.`); - client.ws.close(); - } - else { - client.remoteConnection.writeRaw(packet); - } - } -} diff --git a/logger.js b/logger.js deleted file mode 100644 index ffb53d7..0000000 --- a/logger.js +++ /dev/null @@ -1,54 +0,0 @@ -import { Chalk } from "chalk"; -const color = new Chalk({ level: 2 }); -let global_verbose = false; -export function verboseLogging(newVal) { - global_verbose = (newVal !== null && newVal !== void 0 ? newVal : global_verbose) ? false : true; -} -function jsonLog(type, message) { - return JSON.stringify({ - type: type, - message: message - }) + "\n"; -} -export class Logger { - constructor(name, verbose) { - this.jsonLog = process.argv.includes("--json") || process.argv.includes("-j"); - this.loggerName = name; - if (verbose) - this.verbose = verbose; - else - this.verbose = global_verbose; - } - info(s) { - if (!this.jsonLog) - process.stdout.write(`${color.green("I")} ${color.gray(new Date().toISOString())} ${color.reset(`${color.yellow(`${this.loggerName}:`)} ${s}`)}\n`); - else - process.stdout.write(jsonLog("info", s)); - } - warn(s) { - if (!this.jsonLog) - process.stdout.write(`${color.yellow("W")} ${color.gray(new Date().toISOString())} ${color.yellow(`${color.yellow(`${this.loggerName}:`)} ${s}`)}\n`); - else - process.stderr.write(jsonLog("warn", s)); - } - error(s) { - if (!this.jsonLog) - process.stderr.write(`* ${color.red("E")} ${color.gray(new Date().toISOString())} ${color.redBright(`${color.red(`${this.loggerName}:`)} ${s}`)}\n`); - else - process.stderr.write(jsonLog("error", s)); - } - fatal(s) { - if (!this.jsonLog) - process.stderr.write(`** ${color.red("F!")} ${color.gray(new Date().toISOString())} ${color.bgRedBright(color.redBright(`${color.red(`${this.loggerName}:`)} ${s}`))}\n`); - else - process.stderr.write(jsonLog("fatal", s)); - } - debug(s) { - if (this.verbose || global_verbose) { - if (!this.jsonLog) - process.stderr.write(`${color.gray("D")} ${color.gray(new Date().toISOString())} ${color.gray(`${color.gray(`${this.loggerName}:`)} ${s}`)}\n`); - else - process.stderr.write(jsonLog("debug", s)); - } - } -} diff --git a/meta.js b/meta.js deleted file mode 100644 index a25cb6f..0000000 --- a/meta.js +++ /dev/null @@ -1,3 +0,0 @@ -export const BRANDING = Object.freeze("EaglerXProxy"); -export const VERSION = "1.0.0"; -export const NETWORK_VERSION = Object.freeze(BRANDING + "/" + VERSION); diff --git a/motd.js b/motd.js deleted file mode 100644 index e38ab82..0000000 --- a/motd.js +++ /dev/null @@ -1,34 +0,0 @@ -export function handleMotd(player) { - const names = []; - for (const [username, player] of PROXY.players) { - if (names.length > 0) { - names.push(`(and ${PROXY.players.size - names.length} more)`); - break; - } - else { - names.push(username); - } - } - player.ws.send(JSON.stringify({ - brand: PROXY.brand, - cracked: true, - data: { - cache: true, - icon: PROXY.MOTD.icon ? true : false, - max: PROXY.playerStats.max, - motd: PROXY.MOTD.motd, - online: PROXY.playerStats.onlineCount, - players: names - }, - name: PROXY.serverName, - secure: false, - time: Date.now(), - type: "motd", - uuid: PROXY.proxyUUID, - vers: PROXY.MOTDVersion - })); - if (PROXY.MOTD.icon) { - player.ws.send(PROXY.MOTD.icon); - } - player.ws.close(); -} diff --git a/types.js b/types.js deleted file mode 100644 index 7744d66..0000000 --- a/types.js +++ /dev/null @@ -1,6 +0,0 @@ -export var State; -(function (State) { - State[State["PRE_HANDSHAKE"] = 0] = "PRE_HANDSHAKE"; - State[State["POST_HANDSHAKE"] = 1] = "POST_HANDSHAKE"; - State[State["DISCONNECTED"] = 2] = "DISCONNECTED"; -})(State || (State = {})); diff --git a/utils.js b/utils.js deleted file mode 100644 index 375ab53..0000000 --- a/utils.js +++ /dev/null @@ -1,229 +0,0 @@ -import { v3 } from "uuid"; -import { encodeULEB128 as encodeVarInt, decodeULEB128 as decodeVarInt, decodeSLEB128 as decodeSVarInt } from "@thi.ng/leb128"; -import { DisconnectReason, EaglerPacketId, MAGIC_ENDING_IDENTIFYS_BYTES } from "./eaglerPacketDef.js"; -import { Logger } from "./logger.js"; -import { State } from "./types.js"; -import { toBuffer } from "uuid-buffer"; -import * as mc from "minecraft-protocol"; -import { config } from "./config.js"; -const MAGIC_UUID = "a7e774bc-7ea4-11ed-9a58-1f9e14304a59"; -const logger = new Logger("LoginHandler"); -const USERNAME_REGEX = /[^0-9^a-z^A-Z^_]/gi; -export function genUUID(user) { - return v3(user, MAGIC_UUID); -} -export function bufferizeUUID(uuid) { - return toBuffer(uuid); -} -export function validateUsername(user) { - if (user.length > 20) - throw new Error("Username is too long!"); - if (!!user.match(USERNAME_REGEX)) - throw new Error("Invalid username. Username can only contain alphanumeric characters, and the underscore (_) character."); -} -export function disconnect(player, message, code) { - if (player.state == State.POST_HANDSHAKE) { - const messageLen = encodeVarInt(message.length); - const d = Buffer.alloc(1 + messageLen.length + message.length); - d.set([0x40, ...messageLen, ...Buffer.from(message)]); - player.ws.send(d); - player.ws.close(); - } - else { - const messageLen = encodeVarInt(message.length), codeEnc = encodeVarInt(code !== null && code !== void 0 ? code : DisconnectReason.CUSTOM); - const d = Buffer.alloc(1 + codeEnc.length + messageLen.length + message.length); - d.set([0xff, ...codeEnc, ...messageLen, ...Buffer.from(message)]); - player.ws.send(d); - player.ws.close(); - } -} -export function awaitPacket(ws, id) { - return new Promise((res, rej) => { - let resolved = false; - const msgCb = (msg) => { - if (id != null && msg[0] == id) { - resolved = true; - ws.removeEventListener('message', msgCb); - ws.removeEventListener('close', discon); - ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2); - res(msg); - } - else if (id == null) { - resolved = true; - ws.removeEventListener('message', msgCb); - ws.removeEventListener('close', discon); - ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2); - res(msg); - } - }; - const discon = () => { - resolved = true; - ws.removeEventListener('message', msgCb); - ws.removeEventListener('close', discon); - ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2); - rej("Connection closed"); - }; - ws.setMaxListeners(ws.getMaxListeners() + 2); - ws.on('message', msgCb); - ws.on('close', discon); - setTimeout(() => { - ws.removeEventListener('message', msgCb); - ws.removeEventListener('close', discon); - ws.setMaxListeners(ws.getMaxListeners() - 2 < 0 ? 5 : ws.getMaxListeners() - 2); - rej("Timed out"); - }, 10000); - }); -} -export function loginServer(ip, port, client) { - return new Promise((res, rej) => { - let receivedCompression = false; - const mcClient = mc.createClient({ - host: ip, - port: port, - auth: 'offline', - version: '1.8.8', - username: client.username - }); - mcClient.on('error', err => { - mcClient.end(); - rej(err); - }); - mcClient.on('end', () => { - client.ws.close(); - }); - mcClient.on('connect', () => { - client.remoteConnection = mcClient; - logger.info(`Player ${client.username} has been connected to the server.`); - res(); - }); - mcClient.on('raw', p => { - if (p[0] == 0x03 && !receivedCompression) { - receivedCompression = true; - const compT = { - id: null, - thres: null - }; - const id = decodeVarInt(p); - compT.id = Number(id[0]); - const thres = decodeSVarInt(p.subarray(id[1])); - compT.thres = thres[0]; - client.compressionThreshold = compT.thres; - client.ws.send(p); - } - else { - client.ws.send(p); - } - }); - }); -} -export async function doHandshake(client, initialPacket) { - client.ws.on('close', () => { - client.state = State.DISCONNECTED; - if (client.remoteConnection) { - client.remoteConnection.end(); - } - PROXY.players.delete(client.username); - PROXY.playerStats.onlineCount -= 1; - logger.info(`Client [/${client.ip}:${client.remotePort}]${client.username ? ` (${client.username})` : ""} disconnected from the server.`); - }); - if (PROXY.players.size + 1 > PROXY.playerStats.max) { - disconnect(client, "The proxy is full!", DisconnectReason.CUSTOM); - return; - } - const identifyC = { - id: null, - brandingLen: null, - branding: null, - verLen: null, - ver: null - }; - if (true) { - // save namespace by nesting func declarations in a if true statement - const Iid = decodeVarInt(initialPacket); - identifyC.id = Number(Iid[0]); - const brandingLen = decodeVarInt(initialPacket.subarray(Iid[1] + 2)); - identifyC.brandingLen = Number(brandingLen[0]); - identifyC.branding = initialPacket.subarray(brandingLen[1] + Iid[1] + 2, brandingLen[1] + Iid[1] + Number(brandingLen[0]) + 2).toString(); - const verLen = decodeVarInt(initialPacket.subarray(brandingLen[1] + Iid[1] + Number(brandingLen[0]) + 2)); - identifyC.verLen = Number(verLen[0]); - identifyC.ver = initialPacket.subarray(brandingLen[1] + Number(brandingLen[0]) + Iid[1] + verLen[1] + 2).toString(); - } - if (true) { - const brandingLen = encodeVarInt(PROXY.brand.length), brand = PROXY.brand; - const verLen = encodeVarInt(PROXY.version.length), version = PROXY.version; - const buff = Buffer.alloc(2 + MAGIC_ENDING_IDENTIFYS_BYTES.length + brandingLen.length + brand.length + verLen.length + version.length); - buff.set([EaglerPacketId.IDENTIFY_SERVER, 0x01, ...brandingLen, ...Buffer.from(brand), ...verLen, ...Buffer.from(version), ...Buffer.from(MAGIC_ENDING_IDENTIFYS_BYTES)]); - client.ws.send(buff); - } - client.clientBrand = identifyC.branding; - const login = await awaitPacket(client.ws); - const loginP = { - id: null, - usernameLen: null, - username: null, - randomStrLen: null, - randomStr: null, - nullByte: null - }; - if (login[0] === EaglerPacketId.LOGIN) { - const Iid = decodeVarInt(login); - loginP.id = Number(Iid[0]); - const usernameLen = decodeVarInt(login.subarray(loginP[1])); - loginP.usernameLen = Number(usernameLen[0]); - loginP.username = login.subarray(Iid[1] + usernameLen[1], Iid[1] + usernameLen[1] + loginP.usernameLen).toString(); - const randomStrLen = decodeVarInt(login.subarray(Iid[1] + usernameLen[1] + loginP.usernameLen)); - loginP.randomStrLen = Number(randomStrLen[0]); - loginP.randomStr = login.subarray(Iid[1] + usernameLen[1] + loginP.usernameLen + randomStrLen[1], Iid[1] + usernameLen[1] + loginP.usernameLen + randomStrLen[1] + loginP.randomStrLen).toString(); - client.username = loginP.username; - client.uuid = genUUID(client.username); - try { - validateUsername(client.username); - } - catch (err) { - disconnect(client, err.message, DisconnectReason.BAD_USERNAME); - return; - } - if (PROXY.players.has(client.username)) { - disconnect(client, `Duplicate username: ${client.username}. Please connect under a different username.`, DisconnectReason.DUPLICATE_USERNAME); - return; - } - PROXY.players.set(client.username, client); - if (true) { - const usernameLen = encodeVarInt(client.username.length), username = client.username; - const uuidLen = encodeVarInt(client.uuid.length), uuid = client.uuid; - const buff = Buffer.alloc(1 + usernameLen.length + username.length + uuidLen.length + uuid.length); - buff.set([EaglerPacketId.LOGIN_ACK, ...usernameLen, ...Buffer.from(username), ...uuidLen, ...Buffer.from(uuid)]); - client.ws.send(buff); - if (true) { - const [skin, ready] = await Promise.all([awaitPacket(client.ws, EaglerPacketId.SKIN), awaitPacket(client.ws, EaglerPacketId.C_READY)]); - if (ready[0] != 0x08) { - logger.error(`Client [/${client.ip}:${client.remotePort}] sent an unexpected packet! Disconnecting.`); - disconnect(client, "Received bad packet", DisconnectReason.UNEXPECTED_PACKET); - client.ws.close(); - return; - } - const buff = Buffer.alloc(1); - buff.set([EaglerPacketId.COMPLETE_HANDSHAKE]); - client.ws.send(buff); - client.state = State.POST_HANDSHAKE; - PROXY.playerStats.onlineCount += 1; - logger.info(`Client [/${client.ip}:${client.remotePort}] authenticated as player "${client.username}" and passed handshake. Connecting!`); - try { - await loginServer(config.server.host, config.server.port, client); - } - catch (err) { - logger.error(`Could not connect to remote server at [/${config.server.host}:${config.server.port}]: ${err}`); - disconnect(client, "Failed to connect to server. Please try again later.", DisconnectReason.CUSTOM); - client.state = State.DISCONNECTED; - client.ws.close(); - return; - } - } - } - } - else { - logger.error(`Client [/${client.ip}:${client.remotePort}] sent an unexpected packet! Disconnecting.`); - disconnect(client, "Received bad packet", DisconnectReason.UNEXPECTED_PACKET); - client.ws.close(); - } -}