From 038c4a44d56019fd285c6c7c78b1ede9e93eb9fe Mon Sep 17 00:00:00 2001 From: memetrollsXD Date: Fri, 7 Oct 2022 15:40:32 +0200 Subject: [PATCH] Implement RCON (#72) --- package-lock.json | 11 ++ package.json | 3 +- src/commands/Interface.ts | 24 +-- src/index.ts | 6 +- src/server/rcon/RCONServer.ts | 54 +++++++ src/server/rcon/rcon-server.d.ts | 263 +++++++++++++++++++++++++++++++ src/util/Config.ts | 12 ++ 7 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 src/server/rcon/RCONServer.ts create mode 100644 src/server/rcon/rcon-server.d.ts diff --git a/package-lock.json b/package-lock.json index 310fa2c..ffb54f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "mongodb": "^4.8.0", "node-kcp-token": "github:memetrollsxd/node-kcp", "protobufjs": "^7.0.0", + "rcon-server": "^0.1.1", "typescript": "^4.7.4" }, "devDependencies": { @@ -1202,6 +1203,11 @@ "node": ">= 0.8" } }, + "node_modules/rcon-server": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/rcon-server/-/rcon-server-0.1.1.tgz", + "integrity": "sha512-oJ9+s0yxGc7in6GBBBxFXKRY2OyIVxFsoAEHlo5iFXDCuK6O4gY4qJ9XndMBVmA+nABkyOrN6QAFzoE6wZiI7A==" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2568,6 +2574,11 @@ "unpipe": "1.0.0" } }, + "rcon-server": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/rcon-server/-/rcon-server-0.1.1.tgz", + "integrity": "sha512-oJ9+s0yxGc7in6GBBBxFXKRY2OyIVxFsoAEHlo5iFXDCuK6O4gY4qJ9XndMBVmA+nABkyOrN6QAFzoE6wZiI7A==" + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", diff --git a/package.json b/package.json index 5f4cbe5..6c0cdcb 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "mongodb": "^4.8.0", "node-kcp-token": "github:memetrollsxd/node-kcp", "protobufjs": "^7.0.0", + "rcon-server": "^0.1.1", "typescript": "^4.7.4" } -} \ No newline at end of file +} diff --git a/src/commands/Interface.ts b/src/commands/Interface.ts index 82f0bc8..8cf1a58 100644 --- a/src/commands/Interface.ts +++ b/src/commands/Interface.ts @@ -27,22 +27,14 @@ export default class Interface { private constructor() { } - public static readonly start = () => { + public static start() { Interface.rl.question("", (_command) => { if (!_command) { Interface.start(); return; } const cmd = new Command(_command); - import(`./${alias[cmd.name] || cmd.name}`).then(async module => { - await module.default(cmd); - }).catch(err => { - if (err.code == "MODULE_NOT_FOUND") { - c.log(`Command ${cmd.name} not found.`); - return; - } - c.error(err); - }); + Interface.handle(cmd); Interface.start(); }); @@ -51,4 +43,16 @@ export default class Interface { process.exit(0); }); } + + public static handle(cmd: Command) { + import(`./${alias[cmd.name] || cmd.name}`).then(async module => { + await module.default(cmd); + }).catch(err => { + if (err.code == "MODULE_NOT_FOUND") { + c.log(`Command ${cmd.name} not found.`); + return; + } + c.error(err); + }); + } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index b22d6d5..47a564b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import Interface from "./commands/Interface"; import HttpServer from "./http/HttpServer"; import SRServer from "./server/kcp/SRServer"; +import RCONServer from "./server/rcon/RCONServer"; import Banners from "./util/Banner"; import Logger from "./util/Logger"; import ProtoFactory from "./util/ProtoFactory" @@ -15,6 +16,7 @@ c.log(`Starting CrepeSR...`); Banners.init(); ProtoFactory.init(); -Interface.start(); +Interface.start(); HttpServer.getInstance().start(); -SRServer.getInstance().start(); \ No newline at end of file +SRServer.getInstance().start(); +RCONServer.getInstance().start(); \ No newline at end of file diff --git a/src/server/rcon/RCONServer.ts b/src/server/rcon/RCONServer.ts new file mode 100644 index 0000000..2fa5ad1 --- /dev/null +++ b/src/server/rcon/RCONServer.ts @@ -0,0 +1,54 @@ +// @ts-ignore +import { RCONServer as RServer } from "rcon-server"; +import { Writable } from "stream"; +import Interface, { Command } from "../../commands/Interface"; +import Config from "../../util/Config"; +import Logger from "../../util/Logger"; +const c = new Logger("RCON", "green"); + +export default class RCONServer { + private static _instance: RCONServer; + public RCON!: RServer; + + private constructor() { } + + public static getInstance(): RCONServer { + if (!this._instance) this._instance = new RCONServer(); + return this._instance; + } + + public start() { + if (!Config.RCON.RCON_ENABLED) return; + this.RCON = new RServer({ + host: Config.HTTP.HTTP_HOST, + clientLimit: Config.RCON.RCON_CLIENT_LIMIT, + destroySocketOnLimitExceeded: false, + emitAdvancedEvents: false, + password: Config.RCON.RCON_PASSWORD, + port: Config.RCON.RCON_PORT + }); + + this.RCON.on("listening", () => { + c.log(`Listening on ${Config.RCON.RCON_PORT}`); + }); + + this.RCON.on("commandRequest", (cmd: { + size: number, + id: number, + type: number, + body: string + resolve: (value: string) => void, + }) => { + c.verbL(cmd); + Interface.handle(new Command(cmd.body)); + cmd.resolve(`Command executed.`); + }); + + this.RCON.on("login", ({ pw, fail }: { pw: string, fail: boolean }) => { + c.debug(pw); + fail ? c.log(`Login failed`) : c.log(`Login successful`); // For some reason succ is flipped + }); + + this.RCON.connect(); + } +} \ No newline at end of file diff --git a/src/server/rcon/rcon-server.d.ts b/src/server/rcon/rcon-server.d.ts new file mode 100644 index 0000000..efbfc4c --- /dev/null +++ b/src/server/rcon/rcon-server.d.ts @@ -0,0 +1,263 @@ +/** Declaration file generated by dts-gen */ +import { EventEmitter } from "events"; + +declare module "rcon-server" { + export class RCONServer extends EventEmitter { + constructor(obj: { + port: number = 3839, + host: string = "127.0.0.1", + password: string = "password", + clientLimit: number = 1, + destroySocketOnLimitExceeded: boolean = true, + emitAdvancedEvents: boolean = false + }); + + connect(...args: any[]): void; + + getConnectedSockets(...args: any[]): void; + + getServerSettings(...args: any[]): void; + + getSocketServer(...args: any[]): void; + + static captureRejectionSymbol: any; + + static captureRejections: boolean; + + static defaultMaxListeners: number; + + static errorMonitor: any; + + static getEventListeners(emitterOrTarget: any, type: any): any; + + static init(opts: any): void; + + static kMaxEventTargetListeners: any; + + static kMaxEventTargetListenersWarned: any; + + static listenerCount(emitter: any, type: any): any; + + static on(emitter: any, event: any, options: any): any; + + static once(emitter: any, name: any, options: any): any; + + static setMaxListeners(n: any, eventTargets: any): void; + + static usingDomains: boolean; + + } + + export namespace RCONServer { + class EventEmitter { + constructor(opts: any); + + addListener(type: any, listener: any): any; + + emit(type: any, args: any): any; + + eventNames(): any; + + getMaxListeners(): any; + + listenerCount(type: any): any; + + listeners(type: any): any; + + off(type: any, listener: any): any; + + on(type: any, listener: any): any; + + once(type: any, listener: any): any; + + prependListener(type: any, listener: any): any; + + prependOnceListener(type: any, listener: any): any; + + rawListeners(type: any): any; + + removeAllListeners(type: any, ...args: any[]): any; + + removeListener(type: any, listener: any): any; + + setMaxListeners(n: any): any; + + static EventEmitter: any; + + static captureRejectionSymbol: any; + + static captureRejections: boolean; + + static defaultMaxListeners: number; + + static errorMonitor: any; + + static getEventListeners(emitterOrTarget: any, type: any): any; + + static init(opts: any): void; + + static kMaxEventTargetListeners: any; + + static kMaxEventTargetListenersWarned: any; + + static listenerCount(emitter: any, type: any): any; + + static on(emitter: any, event: any, options: any): any; + + static once(emitter: any, name: any, options: any): any; + + static setMaxListeners(n: any, eventTargets: any): void; + + static usingDomains: boolean; + + } + + class EventEmitterAsyncResource { + constructor(...args: any[]); + + emit(...args: any[]): void; + + emitDestroy(...args: any[]): void; + + static EventEmitterAsyncResource: any; + + static captureRejectionSymbol: any; + + static captureRejections: boolean; + + static defaultMaxListeners: number; + + static errorMonitor: any; + + static getEventListeners(emitterOrTarget: any, type: any): any; + + static init(opts: any): void; + + static kMaxEventTargetListeners: any; + + static kMaxEventTargetListenersWarned: any; + + static listenerCount(emitter: any, type: any): any; + + static on(emitter: any, event: any, options: any): any; + + static once(emitter: any, name: any, options: any): any; + + static setMaxListeners(n: any, eventTargets: any): void; + + static usingDomains: boolean; + + } + + namespace EventEmitter { + class EventEmitterAsyncResource { + constructor(...args: any[]); + + emit(...args: any[]): void; + + emitDestroy(...args: any[]): void; + + static EventEmitter: any; + + static EventEmitterAsyncResource: any; + + static captureRejectionSymbol: any; + + static captureRejections: boolean; + + static defaultMaxListeners: number; + + static errorMonitor: any; + + static getEventListeners(emitterOrTarget: any, type: any): any; + + static init(opts: any): void; + + static kMaxEventTargetListeners: any; + + static kMaxEventTargetListenersWarned: any; + + static listenerCount(emitter: any, type: any): any; + + static on(emitter: any, event: any, options: any): any; + + static once(emitter: any, name: any, options: any): any; + + static setMaxListeners(n: any, eventTargets: any): void; + + static usingDomains: boolean; + + } + + } + + namespace EventEmitterAsyncResource { + class EventEmitter { + constructor(opts: any); + + addListener(type: any, listener: any): any; + + emit(type: any, args: any): any; + + eventNames(): any; + + getMaxListeners(): any; + + listenerCount(type: any): any; + + listeners(type: any): any; + + off(type: any, listener: any): any; + + on(type: any, listener: any): any; + + once(type: any, listener: any): any; + + prependListener(type: any, listener: any): any; + + prependOnceListener(type: any, listener: any): any; + + rawListeners(type: any): any; + + removeAllListeners(type: any, ...args: any[]): any; + + removeListener(type: any, listener: any): any; + + setMaxListeners(n: any): any; + + static EventEmitter: any; + + static EventEmitterAsyncResource: any; + + static captureRejectionSymbol: any; + + static captureRejections: boolean; + + static defaultMaxListeners: number; + + static errorMonitor: any; + + static getEventListeners(emitterOrTarget: any, type: any): any; + + static init(opts: any): void; + + static kMaxEventTargetListeners: any; + + static kMaxEventTargetListenersWarned: any; + + static listenerCount(emitter: any, type: any): any; + + static on(emitter: any, event: any, options: any): any; + + static once(emitter: any, name: any, options: any): any; + + static setMaxListeners(n: any, eventTargets: any): void; + + static usingDomains: boolean; + + } + + } + + } +} \ No newline at end of file diff --git a/src/util/Config.ts b/src/util/Config.ts index 2a68987..b9334cb 100644 --- a/src/util/Config.ts +++ b/src/util/Config.ts @@ -28,6 +28,12 @@ const DEFAULT_CONFIG = { MAINTENANCE: false, MAINTENANCE_MSG: "Server is in maintenance mode." }, + RCON: { + RCON_ENABLED: false, + RCON_PASSWORD: "password", + RCON_PORT: 22103, + RCON_CLIENT_LIMIT: 1 + }, AUTO_ACCOUNT: false } type DefaultConfig = typeof DEFAULT_CONFIG; @@ -82,6 +88,12 @@ export default class Config { MAINTENANCE_MSG: string; } = Config.config.GAMESERVER; public static AUTO_ACCOUNT: boolean = Config.config.AUTO_ACCOUNT; + public static RCON: { + RCON_ENABLED: boolean; + RCON_PASSWORD: string; + RCON_PORT: number; + RCON_CLIENT_LIMIT: number; + } = Config.config.RCON; private constructor() { } }