From 2e75776ff18763d16e327429e603a425a0f111fc Mon Sep 17 00:00:00 2001 From: q13x Date: Tue, 20 Dec 2022 11:31:37 -0800 Subject: [PATCH] Provide options for binding IP and ports --- config.ts | 11 ++++-- index.ts | 51 +++++++++++++++++-------- types.ts | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 148 insertions(+), 25 deletions(-) diff --git a/config.ts b/config.ts index 0afae1c..9017b5c 100644 --- a/config.ts +++ b/config.ts @@ -2,10 +2,11 @@ import { Config } from "./types.js"; export const config: Config = { name: "BasedProxy", - port: 80, // 443 if using TLS + bindHost: "0.0.0.0", + bindPort: 80, // 443 if using TLS maxPlayers: 20, motd: { - iconURL: null, + iconURL: "./icon.webp", l1: "hi", l2: "lol" }, @@ -18,4 +19,8 @@ export const config: Config = { key: null, cert: null } -} \ No newline at end of file +} + +export const BRANDING: Readonly = Object.freeze("EaglerXProxy") +export const VERSION: Readonly = "1.0.0" +export const NETWORK_VERSION: Readonly = Object.freeze(BRANDING + "/" + VERSION) \ No newline at end of file diff --git a/index.ts b/index.ts index 0e9a513..792c65d 100644 --- a/index.ts +++ b/index.ts @@ -2,12 +2,11 @@ import { readFileSync } from "fs"; import * as http from "http" import * as https from "https" import { WebSocketServer } from "ws"; -import { ProxiedPlayer } from "./classes.js"; -import { config } from "./config.js"; +import { BRANDING, config, NETWORK_VERSION, VERSION } 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 { disconnect, generateMOTDImage } from "./utils.js"; +import { ChatColor, ProxiedPlayer, State } from "./types.js"; import { genUUID } from "./utils.js"; const logger = new Logger("EagXProxy") @@ -22,25 +21,20 @@ global.PROXY = { secure: false, proxyUUID: genUUID(config.name), MOTD: { - icon: null, + icon: config.motd.iconURL ? await generateMOTDImage(readFileSync(config.motd.iconURL)) : 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: WebSocketServer if (PROXY.config.security.enabled) { - logger.info(`Starting SECURE WebSocket proxy on port ${config.port}...`) + logger.info(`Starting SECURE WebSocket proxy on port ${config.bindPort}...`) 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.") } @@ -48,29 +42,54 @@ if (PROXY.config.security.enabled) { server: https.createServer({ key: readFileSync(config.security.key), cert: readFileSync(config.security.cert) - }).listen(config.port) + }).listen(config.bindPort, config.bindHost) }) } else { - logger.info(`Starting INSECURE WebSocket proxy on port ${config.port}...`) + logger.info(`Starting INSECURE WebSocket proxy on port ${config.bindPort}...`) server = new WebSocketServer({ - port: config.port + port: config.bindPort, + host: config.bindHost }) } PROXY.wsServer = server server.addListener('connection', c => { - connectionLogger.debug(`[CONNECTION] New inbound WebSocket connection from [/${(c as any)._socket.remoteAddress}:${(c as any)._socket.remotePort}]. (${(c as any)._socket.remotePort} -> ${config.port})`) + connectionLogger.debug(`[CONNECTION] New inbound WebSocket connection from [/${(c as any)._socket.remoteAddress}:${(c as any)._socket.remotePort}]. (${(c as any)._socket.remotePort} -> ${config.bindPort})`) const plr = new ProxiedPlayer() plr.ws = c plr.ip = (c as any)._socket.remoteAddress plr.remotePort = (c as any)._socket.remotePort plr.state = State.PRE_HANDSHAKE + plr.queuedEaglerSkinPackets = [] c.on('message', msg => { handlePacket(msg as Buffer, plr) }) }) server.on('listening', () => { - logger.info(`Successfully started${config.security.enabled ? " [secure]" : ""} WebSocket proxy on port ${config.port}!`) + logger.info(`Successfully started${config.security.enabled ? " [secure]" : ""} WebSocket proxy on port ${config.bindPort}!`) +}) + +process.on('uncaughtException', err => { + logger.error(`An uncaught exception was caught! Exception: ${err.stack ?? err}`) +}) +process.on('unhandledRejection', err => { + logger.error(`An unhandled promise rejection was caught! Rejection: ${(err != null ? (err as any).stack : err) ?? err}`) +}) +process.on('SIGTERM', () => { + logger.info("Cleaning up before exiting...") + for (const [username, plr] of PROXY.players) { + if (plr.remoteConnection != null) plr.remoteConnection.end() + disconnect(plr, ChatColor.YELLOW + "Proxy is shutting down.") + } + process.exit(0) +}) +process.on('SIGINT', () => { + logger.info("Cleaning up before exiting...") + for (const [username, plr] of PROXY.players) { + if (plr.remoteConnection != null) plr.remoteConnection.end() + disconnect(plr, ChatColor.YELLOW + "Proxy is shutting down.") + } + process.exit(0) }) \ No newline at end of file diff --git a/types.ts b/types.ts index 32a2dfa..87c91fd 100644 --- a/types.ts +++ b/types.ts @@ -1,8 +1,9 @@ import { randomUUID } from "crypto" -import { WebSocketServer } from "ws" -import { ProxiedPlayer } from "./classes.js" +import { Client } from "minecraft-protocol" +import { WebSocketServer, WebSocket } from "ws" +import { BRANDING, VERSION, NETWORK_VERSION } from "./config.js" +import { EaglerSkinPacketId, SkinId } from "./eaglerPacketDef.js" import { Logger } from "./logger.js" -import { BRANDING, NETWORK_VERSION, VERSION } from "./meta.js" export type UUID = ReturnType @@ -13,7 +14,7 @@ export enum State { } export type MOTD = { - icon?: Int8Array, // 16384 + icon?: Buffer, // 16384 motd: [string, string] } @@ -32,7 +33,6 @@ export type ProxyGlobals = { proxyUUID: UUID, MOTD: MOTD, - playerStats: PlayerStats, wsServer: WebSocketServer, players: Map, logger: Logger, @@ -41,7 +41,8 @@ export type ProxyGlobals = { export type Config = { name: string, - port: number, + bindPort: number, + bindHost: string, maxPlayers: number, motd: { iconURL?: string, @@ -57,4 +58,102 @@ export type Config = { key: string, cert: string } +} + +export enum ChatColor { + BLACK = "§0", + DARK_BLUE = "§1", + DARK_GREEN = "§2", + DARK_CYAN = "§3", + DARK_RED = "§4", + PURPLE = "§5", + GOLD = "§6", + GRAY = "§7", + DARK_GRAY = "§8", + BLUE = "§9", + BRIGHT_GREEN = "§a", + CYAN = "§b", + RED = "§c", + PINK = "§d", + YELLOW = "§e", + WHITE = "§f", + // text styling + OBFUSCATED = '§k', + BOLD = '§l', + STRIKETHROUGH = '§m', + UNDERLINED = '§n', + ITALIC = '§o', + RESET = '§r' +} + +export type ChatExtra = { + text: string, + bold?: boolean, + italic?: boolean, + underlined?: boolean, + strikethrough?: boolean, + obfuscated?: boolean, + color?: ChatColor | 'reset' +} + +export type Chat = { + text?: string, + bold?: boolean, + italic?: boolean, + underlined?: boolean, + strikethrough?: boolean, + obfuscated?: boolean, + color?: ChatColor | 'reset', + extra?: ChatExtra[] +} + +export class ProxiedPlayer { + public username: string + public uuid: string + public clientBrand: string + public state: State + public ws: WebSocket + public ip: string + public remotePort: number + public remoteConnection: Client + public skin: { + type: "CUSTOM" | "BUILTIN", + skinId?: number, + customSkin?: Buffer + } + public queuedEaglerSkinPackets: UnpackedChannelMessage[] +} + +export enum ChannelMessageType { + CLIENT = 0x17, + SERVER = 0x3f +} + +export type UnpackedChannelMessage = { + channel: string, + data: Buffer, + type: ChannelMessageType +} + +export type DecodedCFetchSkin = { + id: EaglerSkinPacketId.C_FETCH_SKIN, + uuid: UUID +} + +export type DecodedSSkinFetchBuiltin = { + id: EaglerSkinPacketId.S_SKIN_DL_BI, + uuid: UUID, + skinId: SkinId +} + +export type DecodedSSkinDl = { + id: EaglerSkinPacketId.S_SKIN_DL, + uuid: UUID, + skin: Buffer +} + +export type DecodedCSkinReq = { + id: EaglerSkinPacketId.C_REQ_SKIN, + uuid: UUID, + url: string } \ No newline at end of file