Compare commits

...

2 Commits

Author SHA1 Message Date
memetrollsXD
038c4a44d5
Implement RCON (#72) 2022-10-07 15:40:32 +02:00
memetrollsXD
24c7f0e155
Implement kickall command 2022-09-24 11:20:53 +02:00
8 changed files with 374 additions and 13 deletions

11
package-lock.json generated
View File

@ -12,6 +12,7 @@
"mongodb": "^4.8.0", "mongodb": "^4.8.0",
"node-kcp-token": "github:memetrollsxd/node-kcp", "node-kcp-token": "github:memetrollsxd/node-kcp",
"protobufjs": "^7.0.0", "protobufjs": "^7.0.0",
"rcon-server": "^0.1.1",
"typescript": "^4.7.4" "typescript": "^4.7.4"
}, },
"devDependencies": { "devDependencies": {
@ -1202,6 +1203,11 @@
"node": ">= 0.8" "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": { "node_modules/readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -2568,6 +2574,11 @@
"unpipe": "1.0.0" "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": { "readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",

View File

@ -13,6 +13,7 @@
"mongodb": "^4.8.0", "mongodb": "^4.8.0",
"node-kcp-token": "github:memetrollsxd/node-kcp", "node-kcp-token": "github:memetrollsxd/node-kcp",
"protobufjs": "^7.0.0", "protobufjs": "^7.0.0",
"rcon-server": "^0.1.1",
"typescript": "^4.7.4" "typescript": "^4.7.4"
} }
} }

View File

@ -27,13 +27,24 @@ export default class Interface {
private constructor() { } private constructor() { }
public static readonly start = () => { public static start() {
Interface.rl.question("", (_command) => { Interface.rl.question("", (_command) => {
if (!_command) { if (!_command) {
Interface.start(); Interface.start();
return; return;
} }
const cmd = new Command(_command); const cmd = new Command(_command);
Interface.handle(cmd);
Interface.start();
});
Interface.rl.on('close', () => {
console.log('Have a great day!');
process.exit(0);
});
}
public static handle(cmd: Command) {
import(`./${alias[cmd.name] || cmd.name}`).then(async module => { import(`./${alias[cmd.name] || cmd.name}`).then(async module => {
await module.default(cmd); await module.default(cmd);
}).catch(err => { }).catch(err => {
@ -43,12 +54,5 @@ export default class Interface {
} }
c.error(err); c.error(err);
}); });
Interface.start();
});
Interface.rl.on('close', () => {
console.log('Have a great day!');
process.exit(0);
});
} }
} }

14
src/commands/kickall.ts Normal file
View File

@ -0,0 +1,14 @@
import SRServer from "../server/kcp/SRServer";
import Logger from "../util/Logger";
import { Command } from "./Interface";
const c = new Logger("/kickall", "blue");
export default async function handle(command: Command) {
const hard = command.args[0];
for (const [key, session] of SRServer.getInstance().sessions) {
session.kick(!!hard);
}
c.log(`Kicked all players. Hard kick: ${!!hard}`);
}

View File

@ -6,6 +6,7 @@
import Interface from "./commands/Interface"; import Interface from "./commands/Interface";
import HttpServer from "./http/HttpServer"; import HttpServer from "./http/HttpServer";
import SRServer from "./server/kcp/SRServer"; import SRServer from "./server/kcp/SRServer";
import RCONServer from "./server/rcon/RCONServer";
import Banners from "./util/Banner"; import Banners from "./util/Banner";
import Logger from "./util/Logger"; import Logger from "./util/Logger";
import ProtoFactory from "./util/ProtoFactory" import ProtoFactory from "./util/ProtoFactory"
@ -18,3 +19,4 @@ ProtoFactory.init();
Interface.start(); Interface.start();
HttpServer.getInstance().start(); HttpServer.getInstance().start();
SRServer.getInstance().start(); SRServer.getInstance().start();
RCONServer.getInstance().start();

View File

@ -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();
}
}

263
src/server/rcon/rcon-server.d.ts vendored Normal file
View File

@ -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;
}
}
}
}

View File

@ -28,6 +28,12 @@ const DEFAULT_CONFIG = {
MAINTENANCE: false, MAINTENANCE: false,
MAINTENANCE_MSG: "Server is in maintenance mode." MAINTENANCE_MSG: "Server is in maintenance mode."
}, },
RCON: {
RCON_ENABLED: false,
RCON_PASSWORD: "password",
RCON_PORT: 22103,
RCON_CLIENT_LIMIT: 1
},
AUTO_ACCOUNT: false AUTO_ACCOUNT: false
} }
type DefaultConfig = typeof DEFAULT_CONFIG; type DefaultConfig = typeof DEFAULT_CONFIG;
@ -82,6 +88,12 @@ export default class Config {
MAINTENANCE_MSG: string; MAINTENANCE_MSG: string;
} = Config.config.GAMESERVER; } = Config.config.GAMESERVER;
public static AUTO_ACCOUNT: boolean = Config.config.AUTO_ACCOUNT; 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() { } private constructor() { }
} }