From 8b39bd720e8eaedcdec8e7398237dd2e32b53035 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Mon, 6 Nov 2023 22:50:07 +0100 Subject: [PATCH 01/21] feat: updated core/rcon.js with passThrough support and EOSID to SeamID rewriting --- config.json | 2 + core/rcon.js | 807 ++++++++++++++++++++++++------------------ squad-server/index.js | 120 ++++--- 3 files changed, 549 insertions(+), 380 deletions(-) diff --git a/config.json b/config.json index e68f5e5..5ac773e 100644 --- a/config.json +++ b/config.json @@ -5,6 +5,8 @@ "queryPort": 27165, "rconPort": 21114, "rconPassword": "password", + "rconPassThrough": true, + "rconPassThroughPort": 8124, "logReaderMode": "tail", "logDir": "C:/path/to/squad/log/folder", "ftp": { diff --git a/core/rcon.js b/core/rcon.js index 61b2030..f299c01 100644 --- a/core/rcon.js +++ b/core/rcon.js @@ -1,367 +1,502 @@ -import EventEmitter from 'events'; -import net from 'net'; -import util from 'util'; - -import Logger from './logger.js'; - -const SERVERDATA_EXECCOMMAND = 0x02; -const SERVERDATA_RESPONSE_VALUE = 0x00; -const SERVERDATA_AUTH = 0x03; -const SERVERDATA_AUTH_RESPONSE = 0x02; -const SERVERDATA_CHAT_VALUE = 0x01; - -const MID_PACKET_ID = 0x01; -const END_PACKET_ID = 0x02; - +import { EventEmitter } from "node:events"; +import net from "node:net"; +import Logger from "./logger.js"; export default class Rcon extends EventEmitter { constructor(options = {}) { super(); - - // store config - for (const option of ['host', 'port', 'password']) - if (!(option in options)) throw new Error(`${option} must be specified.`); - + for (const option of ["host", "port", "password"]) if (!(option in options)) throw new Error(`${option} must be specified.`); this.host = options.host; this.port = options.port; this.password = options.password; - this.autoReconnectDelay = options.autoReconnectDelay || 5000; - - // bind methods - this.connect = this.connect.bind(this); // we bind this as we call it on the auto reconnect timeout - this.onData = this.onData.bind(this); - this.onClose = this.onClose.bind(this); - this.onError = this.onError.bind(this); - - // setup socket - this.client = new net.Socket(); - this.client.on('data', this.onData); - this.client.on('close', this.onClose); - this.client.on('error', this.onError); - - // constants - this.maximumPacketSize = 4096; - - // internal variables + this.client = null; + this.stream = new Buffer.alloc(0); + this.type = { auth: 0x03, command: 0x02, response: 0x00, server: 0x01 }; + this.soh = { size: 7, id: 0, type: this.type.response, body: "" }; + this.responseString = { id: 0, body: "" }; this.connected = false; this.autoReconnect = false; - this.autoReconnectTimeout = null; + this.autoReconnectDelay = options.autoReconnectDelay || 1000; + this.connectionRetry; + this.msgIdLow = 6; + this.msgIdHigh = 16; + this.specialId = 19; + this.msgId = this.msgIdLow; + this.passThrough = options.passThrough ? true : false; + this.passThroughPort = options.passThroughPort || 8124; + this.passThroughTimeOut = options.passThroughTimeOut || 60000; + this.passThroughMaxClients = 1; //options.passThroughMaxClients || 10; + this.passThroughChallenge = options.passThroughChallenge || options.password; + this.rconClients = {}; + for (let i = 1; i <= this.passThroughMaxClients; i++) this.rconClients[`${i}`] = null; + this.ptServer = null; + + this.steamIndex = { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; // example dtata + this.eosIndex = { "00026e21ce3d43c792613bdbb6dec1ba": "76561198799344716" }; // example dtata - this.incomingData = Buffer.from([]); - this.incomingResponse = []; - this.responseCallbackQueue = []; } - - onData(data) { - Logger.verbose('RCON', 4, `Got data: ${this.bufToHexString(data)}`); - - // the logic in this method simply splits data sent via the data event into packets regardless of how they're - // distributed in the event calls - const packets = this.decodeData(data); - - for (const packet of packets) { - Logger.verbose('RCON', 4, `Processing packet: ${this.bufToHexString(packet)}`); - - const decodedPacket = this.decodePacket(packet); - Logger.verbose( - 'RCON', - 3, - `Processing decoded packet: ${this.decodedPacketToString(decodedPacket)}` - ); - - switch (decodedPacket.type) { - case SERVERDATA_RESPONSE_VALUE: - case SERVERDATA_AUTH_RESPONSE: - switch (decodedPacket.id) { - case MID_PACKET_ID: - this.incomingResponse.push(decodedPacket); - break; - case END_PACKET_ID: - this.responseCallbackQueue.shift()( - this.incomingResponse.map((packet) => packet.body).join() - ); - this.incomingResponse = []; - break; - default: - Logger.verbose( - 'RCON', - 1, - `Unknown packet ID ${decodedPacket.id} in: ${this.decodedPacketToString( - decodedPacket - )}` - ); - } - break; - - case SERVERDATA_CHAT_VALUE: - this.processChatPacket(decodedPacket); - break; - - default: - Logger.verbose( - 'RCON', - 1, - `Unknown packet type ${decodedPacket.type} in: ${this.decodedPacketToString( - decodedPacket - )}` - ); - } - } - } - - decodeData(data) { - this.incomingData = Buffer.concat([this.incomingData, data]); - - const packets = []; - - // we check that it's greater than 4 as if it's not then the length header is not fully present which breaks the - // rest of the code. We just need to wait for more data. - while (this.incomingData.byteLength >= 4) { - const size = this.incomingData.readInt32LE(0); - const packetSize = size + 4; - - // The packet following an empty packet will report to be 10 long (14 including the size header bytes), but in - // it should report 17 long (21 including the size header bytes). Therefore, if the packet is 10 in size - // and there's enough data for it to be a longer packet then we need to probe to check it's this broken packet. - const probeSize = 17; - const probePacketSize = 21; - - if (size === 10 && this.incomingData.byteLength >= probeSize) { - // copy the section of the incoming data of interest - const probeBuf = this.incomingData.slice(0, probePacketSize); - // decode it - const decodedProbePacket = this.decodePacket(probeBuf); - // check whether body matches - if (decodedProbePacket.body === '\x00\x00\x00\x01\x00\x00\x00') { - // it does so it's the broken packet - // remove the broken packet from the incoming data - this.incomingData = this.incomingData.slice(probePacketSize); - Logger.verbose('RCON', 4, `Ignoring some data: ${this.bufToHexString(probeBuf)}`); - continue; - } - } - - if (this.incomingData.byteLength < packetSize) { - Logger.verbose('RCON', 4, `Waiting for more data...`); - break; - } - - const packet = this.incomingData.slice(0, packetSize); - packets.push(packet); - - this.incomingData = this.incomingData.slice(packetSize); - } - - return packets; - } - - decodePacket(packet) { - return { - size: packet.readInt32LE(0), - id: packet.readInt32LE(4), - type: packet.readInt32LE(8), - body: packet.toString('utf8', 12, packet.byteLength - 2) - }; - } - - processChatPacket(decodedPacket) {} - - onClose(hadError) { - this.connected = false; - - Logger.verbose('RCON', 1, `Socket closed ${hadError ? 'without' : 'with'} an error.`); - - if (this.autoReconnect) { - Logger.verbose('RCON', 1, `Sleeping ${this.autoReconnectDelay}ms before reconnecting.`); - setTimeout(this.connect, this.autoReconnectDelay); - } - } - - onError(err) { - Logger.verbose('RCON', 1, `Socket had error:`, err); - this.emit('RCON_ERROR', err); - } - - connect() { + processChatPacket(decodedPacket) { + console.log(decodedPacket.body); + } // + async connect() { return new Promise((resolve, reject) => { - Logger.verbose('RCON', 1, `Connecting to: ${this.host}:${this.port}`); - - const onConnect = async () => { - this.client.removeListener('error', onError); + if (this.client && this.connected && !this.client.destroyed) return reject(new Error("Rcon.connect() Rcon already connected.")); + this.removeAllListeners("server"); + this.removeAllListeners("auth"); + this.on("server", (pkt) => this.processChatPacket(pkt)); + this.once("auth", () => { + Logger.verbose("RCON", 1, `Connected to: ${this.host}:${this.port}`); + clearTimeout(this.connectionRetry); this.connected = true; - - Logger.verbose('RCON', 1, `Connected to: ${this.host}:${this.port}`); - - try { - // connected successfully, now try auth... - await this.write(SERVERDATA_AUTH, this.password); - - // connected and authed successfully - this.autoReconnect = true; - resolve(); - } catch (err) { - reject(err); - } - }; - - const onError = (err) => { - this.client.removeListener('connect', onConnect); - - Logger.verbose('RCON', 1, `Failed to connect to: ${this.host}:${this.port}`, err); - - reject(err); - }; - - this.client.once('connect', onConnect); - this.client.once('error', onError); - - this.client.connect(this.port, this.host); - }); - } - - disconnect() { - return new Promise((resolve, reject) => { - Logger.verbose('RCON', 1, `Disconnecting from: ${this.host}:${this.port}`); - - const onClose = () => { - this.client.removeListener('error', onError); - - Logger.verbose('RCON', 1, `Disconnected from: ${this.host}:${this.port}`); - + if (this.passThrough) this.createServer(); resolve(); - }; - - const onError = (err) => { - this.client.removeListener('close', onClose); - - Logger.verbose('RCON', 1, `Failed to disconnect from: ${this.host}:${this.port}`, err); - - reject(err); - }; - - this.client.once('close', onClose); - this.client.once('error', onError); - - // prevent any auto reconnection happening - this.autoReconnect = false; - // clear the timeout just in case the socket closed and then we DCed - clearTimeout(this.autoReconnectTimeout); - - this.client.end(); + }); + Logger.verbose("RCON", 1, `Connecting to: ${this.host}:${this.port}`); + this.connectionRetry = setTimeout(() => this.connect(), this.autoReconnectDelay); + this.autoReconnect = true; + this.client = net + .createConnection({ port: this.port, host: this.host }, () => this.#sendAuth()) + .on("data", (data) => this.#onData(data)) + .on("end", () => this.#onClose()) + .on("error", () => this.#onNetError()); + }).catch((error) => { + Logger.verbose("RCON", 1, `Rcon.connect() ${error}`); }); } - - execute(command) { - return this.write(SERVERDATA_EXECCOMMAND, command); - } - - write(type, body) { + async disconnect() { return new Promise((resolve, reject) => { - if (!this.connected) { - reject(new Error('Not connected.')); - return; - } - - if (!this.client.writable) { - reject(new Error('Unable to write to socket.')); - return; - } - - const encodedPacket = this.encodePacket( - type, - type !== SERVERDATA_AUTH ? MID_PACKET_ID : END_PACKET_ID, - body - ); - - const encodedEmptyPacket = this.encodePacket(type, END_PACKET_ID, ''); - - if (this.maximumPacketSize < encodedPacket.length) { - reject(new Error('Packet too long.')); - return; - } - - const onError = (err) => { - Logger.verbose('RCON', 1, 'Error occurred. Wiping response action queue.', err); - this.responseCallbackQueue = []; - reject(err); - }; - - // the auth packet also sends a normal response, so we add an extra empty action to ignore it - if (type === SERVERDATA_AUTH) { - Logger.verbose('RCON', 2, `Writing Auth Packet`); - Logger.verbose('RCON', 4, `Writing packet with type "${type}" and body "${body}".`); - this.responseCallbackQueue.push(() => {}); - this.responseCallbackQueue.push((decodedPacket) => { - this.client.removeListener('error', onError); - if (decodedPacket.id === -1) { - Logger.verbose('RCON', 1, 'Authentication failed.'); - reject(new Error('Authentication failed.')); - } else { - Logger.verbose('RCON', 1, 'Authentication succeeded.'); - resolve(); - } - }); - } else { - Logger.verbose('RCON', 2, `Writing packet with type "${type}" and body "${body}".`); - this.responseCallbackQueue.push((response) => { - this.client.removeListener('error', onError); - - Logger.verbose( - 'RCON', - 2, - `Returning complete response: ${response.replace(/\r\n|\r|\n/g, '\\n')}` - ); - - resolve(response); - }); - } - - this.client.once('error', onError); - - Logger.verbose('RCON', 4, `Sending packet: ${this.bufToHexString(encodedPacket)}`); - this.client.write(encodedPacket); - - if (type !== SERVERDATA_AUTH) { - Logger.verbose( - 'RCON', - 4, - `Sending empty packet: ${this.bufToHexString(encodedEmptyPacket)}` - ); - this.client.write(encodedEmptyPacket); - } + Logger.verbose("RCON", 1, `Disconnecting from: ${this.host}:${this.port}`); + clearTimeout(this.connectionRetry); + this.removeAllListeners("server"); + this.removeAllListeners("auth"); + this.autoReconnect = false; + this.client.end(); + this.connected = false; + this.closeServer(); + resolve(); + }).catch((error) => { + Logger.verbose("RCON", 1, `Rcon.disconnect() ${error}`); }); } - - encodePacket(type, id, body, encoding = 'utf8') { + async execute(body) { + return new Promise((resolve, reject) => { + if (!this.connected) return reject(new Error("Rcon not connected.")); + if (!this.client.writable) return reject(new Error("Unable to write to node:net socket")); + const string = String(body); + const length = Buffer.from(string).length; + if (length > 4154) Logger.verbose("RCON", 1, `Error occurred. Oversize, "${length}" > 4154`); + else { + const outputData = (data) => { + clearTimeout(timeOut); + resolve(data); + }; + const timedOut = () => { + console.warn("MISSED", listenerId) + this.removeListener(listenerId, outputData); + return reject(new Error(`Rcon response timed out`)); + }; + if (this.msgId > this.msgIdHigh -2) this.msgId = this.msgIdLow; + const listenerId = `response${this.msgId}`; + const timeOut = setTimeout(timedOut, 10000); + this.once(listenerId, outputData); + this.#send(string, this.msgId); + this.msgId++; + } + }).catch((error) => { + Logger.verbose("RCON", 1, `Rcon.execute() ${error}`); + }); + } + #sendAuth() { + Logger.verbose("RCON", 1, `Sending Token to: ${this.host}:${this.port}`); + this.client.write(this.#encode(this.type.auth, 0, this.password).toString("binary"), "binary");//2147483647 + } + #send(body, id = 99) { + this.#write(this.type.command, id, body); + this.#write(this.type.command, id + 2); + } + #write(type, id, body) { + Logger.verbose("RCON", 2, `Writing packet with type "${type}", id "${id}" and body "${body || ""}"`); + this.client.write(this.#encode(type, id, body).toString("binary"), "binary"); + } + #encode(type, id, body = "") { const size = Buffer.byteLength(body) + 14; - const buf = Buffer.alloc(size); - - buf.writeInt32LE(size - 4, 0); - buf.writeInt32LE(id, 4); - buf.writeInt32LE(type, 8); - buf.write(body, 12, size - 2, encoding); - buf.writeInt16LE(0, size - 2); - - return buf; + const buffer = new Buffer.alloc(size); + buffer.writeInt32LE(size - 4, 0); + buffer.writeInt32LE(id, 4); + buffer.writeInt32LE(type, 8); + buffer.write(body, 12, size - 2, "utf8"); + buffer.writeInt16LE(0, size - 2); + return buffer; } - - bufToHexString(buf) { - return buf.toString('hex').match(/../g).join(' '); + #onData(data) { + console.log(data) + Logger.verbose("RCON", 4, `Got data: ${this.#bufToHexString(data)}`); + this.stream = Buffer.concat([this.stream, data], this.stream.byteLength + data.byteLength); + while (this.stream.byteLength >= 7) { + const packet = this.#decode(); + if (!packet) break; + else Logger.verbose("RCON", 3, `Processing decoded packet: Size: ${packet.size}, ID: ${packet.id}, Type: ${packet.type}, Body: ${packet.body}`); + + if (packet.id > this.msgIdHigh) this.emit(`responseForward_1`, packet); + else if (packet.type === this.type.response) this.#onResponse(packet); + else if (packet.type === this.type.server) this.#onServer(packet); + else if (packet.type === this.type.command) this.emit("auth"); + } } - - decodedPacketToString(decodedPacket) { - return util.inspect(decodedPacket, { breakLength: Infinity }); + #onServer(packet) { + this.emit("server", packet); + for (const client in this.rconClients) + if (this.rconClients[client]) { + this.emit(`serverForward_${this.rconClients[client].rconIdClient}`, packet.body); + } } + #decode() { + if (this.stream[0] === 0 && this.stream[1] === 1 && this.stream[2] === 0 && this.stream[3] === 0 && this.stream[4] === 0 && this.stream[5] === 0 && this.stream[6] === 0) { + this.stream = this.stream.subarray(7); + return this.soh; + } + const bufSize = this.stream.readInt32LE(0); + if (bufSize > 4154 || bufSize < 10) return this.#badPacket(); + else if (bufSize <= this.stream.byteLength - 4 && this.stream.byteLength >= 12) { + const bufId = this.stream.readInt32LE(4); + const bufType = this.stream.readInt32LE(8); + if (this.stream[bufSize + 2] !== 0 || this.stream[bufSize + 3] !== 0 || bufId < 0 || bufType < 0 || bufType > 5) return this.#badPacket(); + else { + const response = { size: bufSize, id: bufId, type: bufType, body: this.stream.toString("utf8", 12, bufSize + 2) }; + this.stream = this.stream.subarray(bufSize + 4); + if (response.body === "" && this.stream[0] === 0 && this.stream[1] === 1 && this.stream[2] === 0 && this.stream[3] === 0 && this.stream[4] === 0 && this.stream[5] === 0 && this.stream[6] === 0) { + this.stream = this.stream.subarray(7); + response.body = ""; + } + return response; + } + } else return null; + } + #onResponse(packet) { + if (packet.body === "") { + this.emit(`response${this.responseString.id - 2}`, this.responseString.body); + this.responseString.body = ""; + } else if (!packet.body.includes("")) { + this.responseString.body = this.responseString.body += packet.body; + this.responseString.id = packet.id; + } else this.#badPacket(); + } + #badPacket() { + Logger.verbose("RCON", 1, `Bad packet, clearing: ${this.bufToHexString(this.stream)} Pending string: ${this.responseString}`); + this.stream = Buffer.alloc(0); + this.responseString = ""; + return null; + } + #onClose() { + Logger.verbose("RCON", 1, `Socket closed`); + this.#cleanUp(); + } + #onNetError(error) { + Logger.verbose("RCON", 1, `node:net error:`, error); + this.emit("RCON_ERROR", error); + this.#cleanUp(); + } + #cleanUp() { + this.closeServer(); + this.connected = false; + this.removeAllListeners(); + clearTimeout(this.connectionRetry); + if (this.autoReconnect) { + Logger.verbose("RCON", 1, `Sleeping ${this.autoReconnectDelay}ms before reconnecting`); + this.connectionRetry = setTimeout(() => this.connect(), this.autoReconnectDelay); + } + } + createServer() { + this.ptServer = net.createServer((client) => this.#onNewClient(client)); + this.ptServer.maxConnections = this.passThroughMaxClients; + this.ptServer.on("error", (error) => this.#onSerErr(error)); + this.ptServer.on("drop", () => Logger.verbose("RCON", 1, `Pass-through Server: Max Clients Reached (${this.passThroughMaxClients}) rejecting new connection`)); + this.ptServer.listen(this.passThroughPort, () => Logger.verbose("RCON", 1, `Pass-through Server: Listening on port ${this.passThroughPort}`)); + } + closeServer() { + for (const client in this.rconClients) if (this.rconClients[client]) this.rconClients[client].end(); + if (!this.ptServer) return; + this.ptServer.close(() => this.#onServerClose()); + } + #onServerClose() { + if (!this.ptServer) return; + this.ptServer.removeAllListeners(); + this.ptServer = null; + Logger.verbose("RCON", 1, `Pass-through Server: Closed`); + } + #onNewClient(client) { + client.setTimeout(this.passThroughTimeOut); + client.on("end", () => this.#onClientEnd(client)); + client.on("error", () => this.#onClientEnd(client)); + client.on("timeout", () => this.#onClientTimeOut(client)); + client.on("data", (data) => this.#onClientData(client, data)); + Logger.verbose("RCON", 1, `Pass-through Server: Client connecting`); + } + #onSerErr(error) { + this.closeServer(); + Logger.verbose("RCON", 1, `Pass-through Server: ${error}`); + } + #onClientEnd(client) { + if (!client.rconIdClient) return; + this.removeAllListeners(`serverForward_${client.rconIdClient}`); + this.removeAllListeners(`responseForward_${client.rconIdClient}`); + this.rconClients[`${client.rconIdClient}`] = null; + Logger.verbose("RCON", 1, `Pass-through Server: Client-${client.rconIdClient} Disconnected`); + } + #onClientTimeOut(client) { + client.end(); + Logger.verbose("RCON", 1, `Pass-through Server: Client-${client.rconIdClient} Timed Out`); + } + #onClientData(client, data) { + if (!client.rconStream) client.rconStream = new Buffer.alloc(0); + client.rconStream = Buffer.concat([client.rconStream, data], client.rconStream.byteLength + data.byteLength); + while (client.rconStream.byteLength >= 4) { + const packet = this.#decodeClient(client); + if (!packet) break; + if (!client.rconHasAuthed) this.#authClient(client, packet); + else { + if (!client.rconWheel || client.rconWheel > 20) client.rconWheel = 0; + else client.rconWheel++; + + client.rconIdQueueNEW[`${client.rconWheel}`] = packet.id + + const encoded = this.#encode(packet.type, this.specialId + client.rconWheel, this.#steamToEosClient(packet.body)); //////////////////////////////////////////////// + this.client.write(encoded.toString("binary"), "binary"); + // this.client.write(this.#encode(packet.type, this.specialId * client.rconIdClient).toString("binary"), "binary") + } + } + } + #decodeClient(client) { + const bufSize = client.rconStream.readInt32LE(0); + if (bufSize <= client.rconStream.byteLength - 4) { + const response = { + size: bufSize, + id: client.rconStream.readInt32LE(4), + type: client.rconStream.readInt32LE(8), + body: client.rconStream.toString("utf8", 12, bufSize + 2), + }; + client.rconStream = client.rconStream.subarray(bufSize + 4); + return response; + } else return null; + } + #authClient(client, packet) { + if (packet.body !== this.passThroughChallenge) { + client.end(); + Logger.verbose("RCON", 1, `Pass-through Server: Client [Rejected] Password not matched`); + } else { + client.rconHasAuthed = true; + client.rconIdQueueNEW = {} + for (let i = 1; i <= this.passThroughMaxClients; i++) { + if (this.rconClients[`${i}`] === null) { + client.rconIdClient = i; + this.rconClients[`${i}`] = client; + break; + } + } + this.on(`serverForward_${client.rconIdClient}`, (body) => client.write(this.#encode(1, 0, this.#eosToSteam(body)).toString("binary"), "binary")); + this.on(`responseForward_${client.rconIdClient}`, (packet) => this.#onForward(client, packet)); + client.write(this.#encode(0, packet.id)); + client.write(this.#encode(2, packet.id)); + Logger.verbose("RCON", 1, `Pass-through Server: Client-${client.rconIdClient} Connected`); + } + } + #onForward(client, packet) { + if (packet.body !== "" && packet.body !== "") { + + const int = packet.id - this.specialId + + //console.log(client.rconIdQueueNEW);////////////////////////////////////////////////////////////////////////////////////////// + + client.write(this.#encode(packet.type, client.rconIdQueueNEW[int], this.#eosToSteam(packet.body)).toString("binary"), "binary"); + } else if (packet.body != "") { + const int = packet.id - this.specialId + client.write(this.#encode(0, client.rconIdQueueNEW[int]).toString("binary"), "binary"); + client.write(this.#encodeSpecial(client.rconIdQueueNEW[int]).toString("binary"), "binary"); + } + } + #encodeSpecial(id) { + const buffer = new Buffer.alloc(21); + buffer.writeInt32LE(10, 0); + buffer.writeInt32LE(id, 4); + buffer.writeInt32LE(0, 8); + buffer.writeInt32LE(1, 15); + return buffer; + } + #bufToHexString(buf) { + return buf.toString("hex").match(/../g).join(" "); + } async warn(steamID, message) { - await this.execute(`AdminWarn "${steamID}" ${message}`); + this.execute(`AdminWarn "${steamID}" ${message}`); } - async kick(steamID, reason) { - await this.execute(`AdminKick "${steamID}" ${reason}`); + this.execute(`AdminKick "${steamID}" ${reason}`); + } + async forceTeamChange(steamID) { + this.execute(`AdminForceTeamChange "${steamID}"`); } - async forceTeamChange(steamID) { - await this.execute(`AdminForceTeamChange "${steamID}"`); + addIds(steamId, eosId) { + this.steamIndex[steamId] = eosId; // { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; + this.eosIndex[eosId] = steamId; } + + removeIds(eosId) { + // clean up ids on leave + } + + #steamToEosClient(body) { + //assume client does not send more than 1 steamId per msg + const m = body.match(/[0-9]{17}/); + if (m && m[1] in this.steamIndex) return body.replaceAll(`${m[0]}`, this.steamIndex[m[0]]); + return body; + } + + #eosToSteam(body) { + //split body to lines for matching (1 steamId per line) + const lines = body.split("\n"); + const nBody = []; + for (let line of lines) nBody.push(this.#matchRcon(line)); + return nBody.join("\n"); + } + + #matchRcon(line) { + console.warn(line); + for (const r of this.defs) { + const match = line.match(r.regex); + if (match && match.groups.eosId in this.eosIndex) return r.rep(line, this.eosIndex[match.groups.eosId], match.groups.eosId); + } + return line; + } + + defs = [ + //strict matching to avoid 'name as steamId errors' + { + regex: /^ID: [0-9]+ \| SteamID: (?[0-9a-f]{32}) \| Name: .+ \| Team ID: (1|2|N\/A) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False|N\/A) \| Role: .+$/, + rep: (line, steamId, eosId) => { + return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); + }, + }, + { + regex: /^ID: (?[0-9]+) \| SteamID: (?[0-9a-f]{32}) \| Since Disconnect: (?.+) \| Name: (?.+)$/, + rep: (line, steamId, eosId) => { + return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); + }, + }, + { + regex: /^ID: (?[0-9]+) \| Name: (?.+) \| Size: (?[0-9]) \| Locked: (?True|False) \| Creator Name: (?.+) \| Creator Steam ID: (?[0-9]{17})/, + rep: (line, steamId, eosId) => { + return line.replace(` Creator Steam ID: ${eosId}`, ` Creator Steam ID: ${steamId}`); + }, + }, + { + regex: /^Forced team change for player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (.+)/, + rep: (line, steamId, eosId) => { + return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); + }, + }, + + { + regex: /^Could not find player (?[0-9a-f]{32})/, + rep: (line, steamId, eosId) => { + return line.replace(`Could not find player ${eosId}`, `Could not find player ${steamId}`); + }, + }, + + { + regex: /^\[ChatAll] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + }, + }, + { + regex: /^\[ChatTeam] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + }, + }, + { + regex: /^\[ChatSquad] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + }, + }, + { + regex: /^\[ChatAdmin] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + }, + }, + { + regex: /^(?.+) \(Steam ID: (?[0-9a-f]{32})\) has created Squad (?[0-9]+) \(Squad Name: (?.+)\) on (?.+)/, + rep: (line, steamId, eosId) => { + return line.replace(` (Steam ID: ${eosId}) `, ` (Steam ID: ${steamId}) `); + }, + }, + { + regex: /^Kicked player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (?.+)/, + rep: (line, steamId, eosId) => { + return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); + }, + }, + + { + regex: /^ERROR: Unable to find player with name or id \((?[0-9a-f]{32})\)$/, + rep: (line, steamId, eosId) => { + return line.replace(`name or id (${eosId})`, `name or id (${steamId})`); + }, + }, + { + regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has possessed admin camera./, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + }, + }, + { + regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has unpossessed admin camera./, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + }, + }, + ]; } + +//////////////////////////////////////////////////ALL BELOW IS FOR STANDALONE TESTING/RUNNING +// const Logger = { +// level: 1, +// verbose(type, lvl, msg, msg1 = "") { +// if (lvl > this.level) return; +// console.log(type, lvl, msg, msg1); +// }, +// }; + +// const squadJsStyle = async () => { +// const getCurrentMap = async () => { +// const response = await rcon.execute("ShowCurrentMap"); +// const match = response.match(/^Current level is (?.+), layer is (?.+)/); +// if (!match) { +// debugger +// } +// return [match.groups.level, match.groups.layer]; +// }; + +// const rcon = new Rcon({ port: "port", host: "ip", password: "password", passThrough: true, passThroughPort: "8124", passThroughTimeOut: 30000, passThroughChallenge: "password" }); // + +// try { +// await rcon.connect(); +// } catch (e) { +// console.warn(e); +// } + +// rcon.interval = setInterval(async () => { + +// try { +// const currentMap = await getCurrentMap(); +// console.log(currentMap); +// } catch (e) { +// console.warn(e); +// } +// }, 5000); +// }; + +// squadJsStyle(); diff --git a/squad-server/index.js b/squad-server/index.js index 490f0a7..e350651 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -19,7 +19,7 @@ export default class SquadServer extends EventEmitter { constructor(options = {}) { super(); - for (const option of ['host', 'queryPort']) + for (const option of [ 'host', 'queryPort' ]) if (!(option in options)) throw new Error(`${option} must be specified.`); this.id = options.id; @@ -95,38 +95,40 @@ export default class SquadServer extends EventEmitter { host: this.options.rconHost || this.options.host, port: this.options.rconPort, password: this.options.rconPassword, - autoReconnectInterval: this.options.rconAutoReconnectInterval + autoReconnectInterval: this.options.rconAutoReconnectInterval, + passThroughPort: this.options.rconPassThroughPort, + passThrough: this.options.rconPassThrough }); this.rcon.on('CHAT_MESSAGE', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.steamID); this.emit('CHAT_MESSAGE', data); const command = data.message.match(/!([^ ]+) ?(.*)/); if (command) - this.emit(`CHAT_COMMAND:${command[1].toLowerCase()}`, { + this.emit(`CHAT_COMMAND:${command[ 1 ].toLowerCase()}`, { ...data, - message: command[2].trim() + message: command[ 2 ].trim() }); }); this.rcon.on('POSSESSED_ADMIN_CAMERA', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.steamID); - this.adminsInAdminCam[data.steamID] = data.time; + this.adminsInAdminCam[ data.steamID ] = data.time; this.emit('POSSESSED_ADMIN_CAMERA', data); }); this.rcon.on('UNPOSSESSED_ADMIN_CAMERA', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); - if (this.adminsInAdminCam[data.steamID]) { - data.duration = data.time.getTime() - this.adminsInAdminCam[data.steamID].getTime(); + data.player = await this.getPlayerByEOSID(data.steamID); + if (this.adminsInAdminCam[ data.steamID ]) { + data.duration = data.time.getTime() - this.adminsInAdminCam[ data.steamID ].getTime(); } else { data.duration = 0; } - delete this.adminsInAdminCam[data.steamID]; + delete this.adminsInAdminCam[ data.steamID ]; this.emit('UNPOSSESSED_ADMIN_CAMERA', data); }); @@ -142,19 +144,19 @@ export default class SquadServer extends EventEmitter { }); this.rcon.on('PLAYER_KICKED', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.steamID); this.emit('PLAYER_KICKED', data); }); this.rcon.on('PLAYER_BANNED', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.steamID); this.emit('PLAYER_BANNED', data); }); this.rcon.on('SQUAD_CREATED', async (data) => { - data.player = await this.getPlayerBySteamID(data.playerSteamID, true); + data.player = await this.getPlayerByEOSID(data.playerSteamID, true); delete data.playerName; delete data.playerSteamID; @@ -207,7 +209,11 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_CONNECTED', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + Logger.verbose("SquadServer", 1, `Player connected ${data.playerSuffix} - SteamID: ${data.steamID} - EOSID: ${data.eosID}`) + + this.rcon.addIds(data.steamID, data.eosID) + + data.player = await this.getPlayerByEOSID(data.steamID); if (data.player) data.player.suffix = data.playerSuffix; delete data.steamID; @@ -217,7 +223,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_DISCONNECTED', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.steamID); delete data.steamID; @@ -310,6 +316,23 @@ export default class SquadServer extends EventEmitter { this.logParser.on('TICK_RATE', (data) => { this.emit('TICK_RATE', data); }); + + this.logParser.on('CLIENT_EXTERNAL_ACCOUNT_INFO', (data) => { + this.rcon.addIds(data.steamID, data.eosID) + }) + // this.logParser.on('CLIENT_CONNECTED', (data) => { + // Logger.verbose("SquadServer", 1, `Client connected. Connection: ${data.connection} - SteamID: ${data.steamID}`) + // }) + // this.logParser.on('CLIENT_LOGIN_REQUEST', (data) => { + // Logger.verbose("SquadServer", 1, `Login request. ChainID: ${data.chainID} - Suffix: ${data.suffix} - EOSID: ${data.eosID}`) + + // }) + // this.logParser.on('RESOLVED_EOS_ID', (data) => { + // Logger.verbose("SquadServer", 1, `Resolved EOSID. ChainID: ${data.chainID} - Suffix: ${data.suffix} - EOSID: ${data.eosID}`) + // }) + // this.logParser.on('ADDING_CLIENT_CONNECTION', (data) => { + // Logger.verbose("SquadServer", 1, `Adding client connection`, data) + // }) } async restartLogParser() { @@ -325,12 +348,12 @@ export default class SquadServer extends EventEmitter { } getAdminPermsBySteamID(steamID) { - return this.admins[steamID]; + return this.admins[ steamID ]; } getAdminsWithPermission(perm) { const ret = []; - for (const [steamID, perms] of Object.entries(this.admins)) { + for (const [ steamID, perms ] of Object.entries(this.admins)) { if (perm in perms) ret.push(steamID); } return ret; @@ -348,13 +371,13 @@ export default class SquadServer extends EventEmitter { try { const oldPlayerInfo = {}; for (const player of this.players) { - oldPlayerInfo[player.steamID] = player; + oldPlayerInfo[ player.steamID ] = player; } const players = []; - for (const player of await this.rcon.getListPlayers()) + for (const player of await this.rcon.getListPlayers(this)) players.push({ - ...oldPlayerInfo[player.steamID], + ...oldPlayerInfo[ player.steamID ], ...player, playercontroller: this.logParser.eventStore.players[player.steamID] ? this.logParser.eventStore.players[player.steamID].controller @@ -365,17 +388,17 @@ export default class SquadServer extends EventEmitter { this.players = players; for (const player of this.players) { - if (typeof oldPlayerInfo[player.steamID] === 'undefined') continue; - if (player.teamID !== oldPlayerInfo[player.steamID].teamID) + if (typeof oldPlayerInfo[ player.steamID ] === 'undefined') continue; + if (player.teamID !== oldPlayerInfo[ player.steamID ].teamID) this.emit('PLAYER_TEAM_CHANGE', { player: player, - oldTeamID: oldPlayerInfo[player.steamID].teamID, + oldTeamID: oldPlayerInfo[ player.steamID ].teamID, newTeamID: player.teamID }); - if (player.squadID !== oldPlayerInfo[player.steamID].squadID) + if (player.squadID !== oldPlayerInfo[ player.steamID ].squadID) this.emit('PLAYER_SQUAD_CHANGE', { player: player, - oldSquadID: oldPlayerInfo[player.steamID].squadID, + oldSquadID: oldPlayerInfo[ player.steamID ].squadID, newSquadID: player.squadID }); } @@ -447,26 +470,32 @@ export default class SquadServer extends EventEmitter { Logger.verbose('SquadServer', 1, `Updating A2S information...`); try { - const data = await Gamedig.query({ - type: 'squad', - host: this.options.host, - port: this.options.queryPort - }); + // const data = await Gamedig.query({ + // type: 'squad', + // host: this.options.host, + // port: this.options.queryPort + // }); + + const rawData = await this.rcon.execute(`ShowServerInfo`); + Logger.verbose("SquadServer", 3, `A2S raw data`, rawData) + const data = JSON.parse(rawData); + Logger.verbose("SquadServer", 2, `A2S data`, JSON.data) + // Logger.verbose("SquadServer", 1, `A2S data`, JSON.stringify(data, null, 2)) const info = { - raw: data.raw, + raw: data, serverName: data.name, - maxPlayers: parseInt(data.maxplayers), - publicSlots: parseInt(data.raw.rules.NUMPUBCONN), - reserveSlots: parseInt(data.raw.rules.NUMPRIVCONN), + maxPlayers: parseInt(data.MaxPlayers), + publicSlots: parseInt(data.PublicQueueLimit_I), + reserveSlots: parseInt(data.PlayerReserveCount_I), - a2sPlayerCount: parseInt(data.raw.rules.PlayerCount_i), - publicQueue: parseInt(data.raw.rules.PublicQueue_i), - reserveQueue: parseInt(data.raw.rules.ReservedQueue_i), + a2sPlayerCount: parseInt(data.PlayerCount_I), + publicQueue: parseInt(data.PublicQueue_I), + reserveQueue: parseInt(data.ReservedQueue_I), - matchTimeout: parseFloat(data.raw.rules.MatchTimeout_f), - gameVersion: data.raw.version + matchTimeout: parseFloat(data.MatchTimeout_d), + gameVersion: data.GameVersion_s }; this.serverName = info.serverName; @@ -500,7 +529,7 @@ export default class SquadServer extends EventEmitter { if (!forceUpdate) { matches = this.players.filter(condition); - if (matches.length === 1) return matches[0]; + if (matches.length === 1) return matches[ 0 ]; if (!retry) return null; } @@ -508,7 +537,7 @@ export default class SquadServer extends EventEmitter { await this.updatePlayerList(); matches = this.players.filter(condition); - if (matches.length === 1) return matches[0]; + if (matches.length === 1) return matches[ 0 ]; return null; } @@ -518,7 +547,7 @@ export default class SquadServer extends EventEmitter { if (!forceUpdate) { matches = this.squads.filter(condition); - if (matches.length === 1) return matches[0]; + if (matches.length === 1) return matches[ 0 ]; if (!retry) return null; } @@ -526,7 +555,7 @@ export default class SquadServer extends EventEmitter { await this.updateSquadList(); matches = this.squads.filter(condition); - if (matches.length === 1) return matches[0]; + if (matches.length === 1) return matches[ 0 ]; return null; } @@ -541,6 +570,9 @@ export default class SquadServer extends EventEmitter { async getPlayerBySteamID(steamID, forceUpdate) { return this.getPlayerByCondition((player) => player.steamID === steamID, forceUpdate); } + async getPlayerByEOSID(eosID, forceUpdate) { + return this.getPlayerByCondition((player) => player.EOSID === eosID, forceUpdate); + } async getPlayerByName(name, forceUpdate) { return this.getPlayerByCondition((player) => player.name === name, forceUpdate); From cb420866cddd369682e3847888c7b1477b37689f Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Mon, 6 Nov 2023 22:53:29 +0100 Subject: [PATCH 02/21] chore: improved eventstore default objects --- core/log-parser/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/log-parser/index.js b/core/log-parser/index.js index 2a91ebb..d504736 100644 --- a/core/log-parser/index.js +++ b/core/log-parser/index.js @@ -16,9 +16,13 @@ export default class LogParser extends EventEmitter { this.eventStore = { disconnected: {}, // holding area, cleared on map change. - players: {}, // persistent data, steamid, controller, suffix. + players: [], // persistent data, steamid, controller, suffix. + playersEOS: [], // proxies from EOSID to persistent data, steamid, controller, suffix. + connectionIdToSteamID: new Map(), session: {}, // old eventstore, nonpersistent data - clients: {} // used in the connection chain before we resolve a player. + clients: {}, // used in the connection chain before we resolve a player. + lastConnection: {}, // used to store the last client connection data to then associate a steamid + joinRequests: [] }; this.linesPerMinute = 0; From 9878e553241c9ab9380af7119f20d03a37e088b6 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Mon, 6 Nov 2023 23:02:50 +0100 Subject: [PATCH 03/21] fix: updated regular expressions for matching EOSIDs in rcon.js --- squad-server/rcon.js | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/squad-server/rcon.js b/squad-server/rcon.js index b618ba4..dcef0af 100644 --- a/squad-server/rcon.js +++ b/squad-server/rcon.js @@ -1,10 +1,11 @@ import Logger from 'core/logger'; import Rcon from 'core/rcon'; +import PersistentEOSIDtoSteamID from './plugins/persistent-eosid-to-steamid.js'; export default class SquadRcon extends Rcon { processChatPacket(decodedPacket) { const matchChat = decodedPacket.body.match( - /\[(ChatAll|ChatTeam|ChatSquad|ChatAdmin)] \[SteamID:([0-9]{17})] (.+?) : (.*)/ + /\[(ChatAll|ChatTeam|ChatSquad|ChatAdmin)] \[SteamID:([0-9a-f]{32})] (.+?) : (.*)/ ); if (matchChat) { Logger.verbose('SquadRcon', 2, `Matched chat message: ${decodedPacket.body}`); @@ -22,7 +23,7 @@ export default class SquadRcon extends Rcon { } const matchPossessedAdminCam = decodedPacket.body.match( - /\[SteamID:([0-9]{17})] (.+?) has possessed admin camera./ + /\[SteamID:([0-9a-f]{32})] (.+?) has possessed admin camera./ ); if (matchPossessedAdminCam) { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); @@ -37,7 +38,7 @@ export default class SquadRcon extends Rcon { } const matchUnpossessedAdminCam = decodedPacket.body.match( - /\[SteamID:([0-9]{17})] (.+?) has unpossessed admin camera./ + /\[SteamID:([0-9a-f]{32})] (.+?) has unpossessed admin camera./ ); if (matchUnpossessedAdminCam) { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); @@ -68,7 +69,7 @@ export default class SquadRcon extends Rcon { } const matchKick = decodedPacket.body.match( - /Kicked player ([0-9]+)\. \[steamid=([0-9]{17})] (.*)/ + /Kicked player ([0-9]+)\. \[steamid=([0-9a-f]{32})] (.*)/ ); if (matchKick) { Logger.verbose('SquadRcon', 2, `Matched kick message: ${decodedPacket.body}`); @@ -85,7 +86,7 @@ export default class SquadRcon extends Rcon { } const matchSqCreated = decodedPacket.body.match( - /(.+) \(Steam ID: ([0-9]{17})\) has created Squad (\d+) \(Squad Name: (.+)\) on (.+)/ + /(.+) \(Steam ID: ([0-9a-f]{32})\) has created Squad (\d+) \(Squad Name: (.+)\) on (.+)/ ); if (matchSqCreated) { Logger.verbose('SquadRcon', 2, `Matched Squad Created: ${decodedPacket.body}`); @@ -134,22 +135,32 @@ export default class SquadRcon extends Rcon { }; } - async getListPlayers() { + async getListPlayers(server) { const response = await this.execute('ListPlayers'); const players = []; if(!response || response.length < 1) return players; - + for (const line of response.split('\n')) { const match = line.match( - /ID: ([0-9]+) \| SteamID: ([0-9]{17}) \| Name: (.+) \| Team ID: ([0-9]+) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False) \| Role: ([A-Za-z0-9_]*)\b/ + /ID: ([0-9]+) \| SteamID: ([0-9a-f]{32}) \| Name: (.+) \| Team ID: ([0-9]+) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False) \| Role: ([A-Za-z0-9_]*)\b/ ); if (!match) continue; + let steamID = this.eosIndex[ match[ 2 ] ] + + const persEosIdPlugin = server.plugins.find(p => p instanceof PersistentEOSIDtoSteamID) + if (persEosIdPlugin && !this.eosIndex[ match[ 2 ] ]) { + steamID = (await persEosIdPlugin.getByEOSID(match[ 2 ])).steamID + this.addIds(steamID, match[ 2 ]); + Logger.verbose("RCON", 1, `Getting SteamID for player: ${match[ 3 ]} from EOSID: ${match[ 2 ]} => ${steamID}`) + } + players.push({ playerID: match[1], - steamID: match[2], + steamID: steamID, + EOSID: match[2], name: match[3], teamID: match[4], squadID: match[5] !== 'N/A' ? match[5] : null, @@ -168,11 +179,9 @@ export default class SquadRcon extends Rcon { let teamName; let teamID; - if(!responseSquad || responseSquad.length < 1) return squads; - for (const line of responseSquad.split('\n')) { const match = line.match( - /ID: ([0-9]+) \| Name: (.+) \| Size: ([0-9]+) \| Locked: (True|False) \| Creator Name: (.+) \| Creator Steam ID: ([0-9]{17})/ + /ID: ([0-9]+) \| Name: (.+) \| Size: ([0-9]+) \| Locked: (True|False) \| Creator Name: (.+) \| Creator Steam ID: ([0-9a-f]{32})/ ); const matchSide = line.match(/Team ID: (1|2) \((.+)\)/); if (matchSide) { From 4b0ee2023f114e21c8462236023ee66afabfa33d Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Mon, 6 Nov 2023 23:06:01 +0100 Subject: [PATCH 04/21] feat: updated log-parser with matching regex and new connection sequence --- .../log-parser/adding-client-connection.js | 20 +++++++++++ .../check-permission-resolve-eosid.js | 15 ++++++++ .../client-external-account-info.js | 21 ++++++++++++ squad-server/log-parser/client-login.js | 14 ++++---- squad-server/log-parser/index.js | 17 +++++++--- squad-server/log-parser/join-request.js | 16 +++++++++ squad-server/log-parser/login-request.js | 17 ++++++++++ .../pending-connection-destroyed.js | 2 +- squad-server/log-parser/player-connected.js | 34 +++++++++++-------- .../log-parser/player-disconnected.js | 2 +- .../log-parser/playercontroller-connected.js | 11 +++--- .../log-parser/sending-auth-result.js | 21 ++++++++++++ 12 files changed, 156 insertions(+), 34 deletions(-) create mode 100644 squad-server/log-parser/adding-client-connection.js create mode 100644 squad-server/log-parser/check-permission-resolve-eosid.js create mode 100644 squad-server/log-parser/client-external-account-info.js create mode 100644 squad-server/log-parser/join-request.js create mode 100644 squad-server/log-parser/login-request.js create mode 100644 squad-server/log-parser/sending-auth-result.js diff --git a/squad-server/log-parser/adding-client-connection.js b/squad-server/log-parser/adding-client-connection.js new file mode 100644 index 0000000..b4011e9 --- /dev/null +++ b/squad-server/log-parser/adding-client-connection.js @@ -0,0 +1,20 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: AddClientConnection: Added client connection: \[UNetConnection\] RemoteAddr: ([\d\.]+):[0-9]+, Name: (EOSIpNetConnection_[0-9]+), Driver: GameNetDriver (EOSNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: INVALID/, + onMatch: (args, logParser) => { + const data = { + raw: args[ 0 ], + time: args[ 1 ], + chainID: args[ 2 ], + // steamID: args[ 3 ], + ip: args[ 3 ], + connection: args[ 4 ], + driver: args[ 5 ] + }; + /* This is Called when unreal engine adds a client connection + First Step in Adding a Player to server + */ + logParser.eventStore[ 'last-connection' ] = data; + logParser.emit('ADDING_CLIENT_CONNECTION', data); + } +}; diff --git a/squad-server/log-parser/check-permission-resolve-eosid.js b/squad-server/log-parser/check-permission-resolve-eosid.js new file mode 100644 index 0000000..853200e --- /dev/null +++ b/squad-server/log-parser/check-permission-resolve-eosid.js @@ -0,0 +1,15 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadCommon: SQCommonStatics Check Permissions, UniqueId:([\da-f]+)$/, + onMatch: (args, logParser) => { + const data = { + raw: args[ 0 ], + time: args[ 1 ], + chainID: +args[ 2 ], + eosID: args[ 3 ], + }; + + logParser.eventStore.joinRequests[ data.chainID ].eosID = data.eosID; + logParser.emit('RESOLVED_EOS_ID', { ...logParser.eventStore.joinRequests[ data.chainID ] }); + } +}; diff --git a/squad-server/log-parser/client-external-account-info.js b/squad-server/log-parser/client-external-account-info.js new file mode 100644 index 0000000..693016c --- /dev/null +++ b/squad-server/log-parser/client-external-account-info.js @@ -0,0 +1,21 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]+)]LogEOS: Verbose: \[LogEOSConnect] FConnectClient::CacheExternalAccountInfo - ProductUserId: (?[0-9a-f]{32}), AccountType: (\d), AccountId: (?[0-9]{17}), DisplayName: /, + onMatch: (args, logParser) => { + const data = { + raw: args[ 0 ], + time: args[ 1 ], + chainID: args[ 2 ], + eosID: args.groups.eosId, + steamID: args.groups.steamId, + }; + + logParser.eventStore.players[ data.steamID ] = { + eosID: data.eosID, + steamID: data.steamID + }; + logParser.eventStore.playersEOS[ data.eosID ] = logParser.eventStore.players[ data.steamID ] + + logParser.emit('CLIENT_EXTERNAL_ACCOUNT_INFO', data); + } +}; diff --git a/squad-server/log-parser/client-login.js b/squad-server/log-parser/client-login.js index 20b84d7..7f2406f 100644 --- a/squad-server/log-parser/client-login.js +++ b/squad-server/log-parser/client-login.js @@ -1,20 +1,20 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Login: NewPlayer: SteamNetConnection \/Engine\/Transient\.(SteamNetConnection_[0-9]+)/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Login: NewPlayer: EOSIpNetConnection \/Engine\/Transient\.(EOSIpNetConnection_[0-9]+)/, onMatch: (args, logParser) => { const data = { - raw: args[0], - time: args[1], - chainID: args[2], - connection: args[3] + raw: args[ 0 ], + time: args[ 1 ], + chainID: args[ 2 ], + connection: args[ 3 ] }; /* This is Called when a player begins the Login process We use this to get a SteamID into playerConnected. 2nd Step in player connected path */ - logParser.eventStore['client-login'] = logParser.eventStore.clients[args[3]]; - delete logParser.eventStore.clients[args[3]]; + logParser.eventStore.joinRequests[ data.chainID ].connection = data.connection; + delete logParser.eventStore.clients[ args[ 3 ] ]; logParser.emit('CLIENT_LOGIN', data); } }; diff --git a/squad-server/log-parser/index.js b/squad-server/log-parser/index.js index 594734c..2223b33 100644 --- a/squad-server/log-parser/index.js +++ b/squad-server/log-parser/index.js @@ -16,10 +16,14 @@ import RoundEnded from './round-ended.js'; import RoundTickets from './round-tickets.js'; import RoundWinner from './round-winner.js'; import ServerTickRate from './server-tick-rate.js'; -import ClientConnected from './client-connected.js'; +import AddingClientConnection from './adding-client-connection.js'; import ClientLogin from './client-login.js'; import PendingConnectionDestroyed from './pending-connection-destroyed.js'; - +import clientExternalAccountInfo from './client-external-account-info.js'; +import sendingAuthResult from './sending-auth-result.js'; +import loginRequest from './login-request.js'; +import joinRequest from './join-request.js'; +import checkPermissionResolveEosid from './check-permission-resolve-eosid.js'; export default class SquadLogParser extends LogParser { constructor(options) { super('SquadGame.log', options); @@ -43,9 +47,14 @@ export default class SquadLogParser extends LogParser { RoundTickets, RoundWinner, ServerTickRate, - ClientConnected, + AddingClientConnection, ClientLogin, - PendingConnectionDestroyed + PendingConnectionDestroyed, + clientExternalAccountInfo, + sendingAuthResult, + loginRequest, + joinRequest, + checkPermissionResolveEosid, ]; } } diff --git a/squad-server/log-parser/join-request.js b/squad-server/log-parser/join-request.js new file mode 100644 index 0000000..3fdfd3f --- /dev/null +++ b/squad-server/log-parser/join-request.js @@ -0,0 +1,16 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join request: .+\?Name=(.+)\?SplitscreenCount=\d$/, + onMatch: (args, logParser) => { + const data = { + raw: args[ 0 ], + time: args[ 1 ], + chainID: +args[ 2 ], + suffix: args[ 3 ], + }; + + logParser.eventStore.joinRequests[ data.chainID ] = data; + // console.log(logParser.eventStore.loginRequests[ data.chainID ]) + logParser.emit('CLIENT_JOIN_REQUEST', data); + } +}; diff --git a/squad-server/log-parser/login-request.js b/squad-server/log-parser/login-request.js new file mode 100644 index 0000000..e1cc822 --- /dev/null +++ b/squad-server/log-parser/login-request.js @@ -0,0 +1,17 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Login request: \?Name=(.+) userId: RedpointEOS:([\da-f]{32}) platform: RedpointEOS/, + onMatch: (args, logParser) => { + const data = { + raw: args[ 0 ], + time: args[ 1 ], + chainID: +args[ 2 ], + suffix: args[ 3 ], + eosID: args[ 4 ] + }; + + // logParser.eventStore.loginRequests[ data.chainID ] = data; + // console.log(logParser.eventStore.loginRequests[ data.chainID ]) + logParser.emit('CLIENT_LOGIN_REQUEST', data); + } +}; diff --git a/squad-server/log-parser/pending-connection-destroyed.js b/squad-server/log-parser/pending-connection-destroyed.js index 3d4aa1c..5ec3239 100644 --- a/squad-server/log-parser/pending-connection-destroyed.js +++ b/squad-server/log-parser/pending-connection-destroyed.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UNetConnection::PendingConnectionLost\. \[UNetConnection\] RemoteAddr: ([0-9]{17}):[0-9]+, Name: (SteamNetConnection_[0-9]+), Driver: GameNetDriver (SteamNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: (?:Steam:UNKNOWN \[.+\]|INVALID) bPendingDestroy=0/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UNetConnection::PendingConnectionLost\. \[UNetConnection\] RemoteAddr: ([0-9a-f]{32}):[0-9]+, Name: (SteamNetConnection_[0-9]+), Driver: GameNetDriver (SteamNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: (?:Steam:UNKNOWN \[.+\]|INVALID) bPendingDestroy=0/, onMatch: (args, logParser) => { const data = { raw: args[0], diff --git a/squad-server/log-parser/player-connected.js b/squad-server/log-parser/player-connected.js index 279257d..9e768c2 100644 --- a/squad-server/log-parser/player-connected.js +++ b/squad-server/log-parser/player-connected.js @@ -2,26 +2,30 @@ export default { regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join succeeded: (.+)/, onMatch: (args, logParser) => { const data = { - raw: args[0], - time: args[1], - chainID: args[2], - playerSuffix: args[3], - steamID: logParser.eventStore['client-login'], // player connected - controller: logParser.eventStore['player-controller'] // playercontroller connected + raw: args[ 0 ], + time: args[ 1 ], + chainID: +args[ 2 ], + playerSuffix: args[ 3 ] }; - delete logParser.eventStore['client-login']; - delete logParser.eventStore['player-controller']; + // console.log(`ChainID: ${data.chainID}`, logParser.eventStore.joinRequests[ data.chainID ]); + const joinRequestsData = { ...logParser.eventStore.joinRequests[ data.chainID ] }; + // console.log('loginRequestData', loginRequestData) + + data.eosID = joinRequestsData.eosID + data.controller = joinRequestsData.controller + data.steamID = `${logParser.eventStore.connectionIdToSteamID.get(joinRequestsData.connection)}` + + logParser.eventStore.connectionIdToSteamID.delete(joinRequestsData.connection) + + delete logParser.eventStore.joinRequests[ +data.chainID ]; // Handle Reconnecting players - if (logParser.eventStore.disconnected[data.steamID]) { - delete logParser.eventStore.disconnected[data.steamID]; + if (logParser.eventStore.disconnected[ data.steamID ]) { + delete logParser.eventStore.disconnected[ data.steamID ]; } logParser.emit('PLAYER_CONNECTED', data); - logParser.eventStore.players[data.steamID] = { - steamID: data.steamID, - suffix: data.playerSuffix, - controller: data.controller - }; + // logParser.eventStore.players[ data.steamID ].suffix = data.playerSuffix + // logParser.eventStore.players[ data.steamID ].controller = data.controller } }; diff --git a/squad-server/log-parser/player-disconnected.js b/squad-server/log-parser/player-disconnected.js index 0997697..9fd86e9 100644 --- a/squad-server/log-parser/player-disconnected.js +++ b/squad-server/log-parser/player-disconnected.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UChannel::Close: Sending CloseBunch\. ChIndex == [0-9]+\. Name: \[UChannel\] ChIndex: [0-9]+, Closing: [0-9]+ \[UNetConnection\] RemoteAddr: ([0-9]{17}):[0-9]+, Name: SteamNetConnection_[0-9]+, Driver: GameNetDriver SteamNetDriver_[0-9]+, IsServer: YES, PC: ([^ ]+PlayerController_C_[0-9]+), Owner: [^ ]+PlayerController_C_[0-9]+/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UChannel::Close: Sending CloseBunch\. ChIndex == [0-9]+\. Name: \[UChannel\] ChIndex: [0-9]+, Closing: [0-9]+ \[UNetConnection\] RemoteAddr: ([0-9a-f]{32}):[0-9]+, Name: SteamNetConnection_[0-9]+, Driver: GameNetDriver SteamNetDriver_[0-9]+, IsServer: YES, PC: ([^ ]+PlayerController_C_[0-9]+), Owner: [^ ]+PlayerController_C_[0-9]+/, onMatch: (args, logParser) => { const data = { raw: args[0], diff --git a/squad-server/log-parser/playercontroller-connected.js b/squad-server/log-parser/playercontroller-connected.js index 18a0216..0e8040d 100644 --- a/squad-server/log-parser/playercontroller-connected.js +++ b/squad-server/log-parser/playercontroller-connected.js @@ -3,14 +3,13 @@ export default { /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: PostLogin: NewPlayer: BP_PlayerController_C .+(BP_PlayerController_C_[0-9]+)/, onMatch: (args, logParser) => { const data = { - raw: args[0], - time: args[1], - chainID: args[2], - controller: args[3] + raw: args[ 0 ], + time: args[ 1 ], + chainID: +args[ 2 ], + controller: args[ 3 ] }; - logParser.eventStore['player-controller'] = args[3]; - + logParser.eventStore.joinRequests[ data.chainID ].controller = data.controller; logParser.emit('PLAYER_CONTROLLER_CONNECTED', data); } }; diff --git a/squad-server/log-parser/sending-auth-result.js b/squad-server/log-parser/sending-auth-result.js new file mode 100644 index 0000000..ce0ca8c --- /dev/null +++ b/squad-server/log-parser/sending-auth-result.js @@ -0,0 +1,21 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogOnline: STEAM: AUTH HANDLER: Sending auth result to user (\d{17}) with flag success\? 1/, + onMatch: (args, logParser) => { + if (!logParser.eventStore[ 'last-connection' ]) return; + + const data = { + ...logParser.eventStore[ 'last-connection' ], + steamID: args[ 3 ] + }; + /* This is Called when unreal engine adds a client connection + First Step in Adding a Player to server + */ + + logParser.eventStore.clients[ data.connection ] = data.steamID; + logParser.eventStore.connectionIdToSteamID.set(data.connection, data.steamID) + logParser.emit('CLIENT_CONNECTED', data); + + delete logParser.eventStore[ 'last-connection' ]; + } +}; From c43fec5fc1bdc728771addc862f5a3f43860cab1 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Mon, 6 Nov 2023 23:07:23 +0100 Subject: [PATCH 05/21] feat: plugin to store in a database known steamid-eosid associations --- config.json | 5 + .../plugins/persistent-eosid-to-steamid.js | 91 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 squad-server/plugins/persistent-eosid-to-steamid.js diff --git a/config.json b/config.json index 5ac773e..b707e35 100644 --- a/config.json +++ b/config.json @@ -42,6 +42,11 @@ "sqlite": "sqlite:database.sqlite" }, "plugins": [ + { + "plugin": "PersistentEOSIDtoSteamID", + "enabled": true, + "database": "sqlite" + }, { "plugin": "AutoKickUnassigned", "enabled": true, diff --git a/squad-server/plugins/persistent-eosid-to-steamid.js b/squad-server/plugins/persistent-eosid-to-steamid.js new file mode 100644 index 0000000..0fa0598 --- /dev/null +++ b/squad-server/plugins/persistent-eosid-to-steamid.js @@ -0,0 +1,91 @@ +import Sequelize from 'sequelize'; + +import BasePlugin from './base-plugin.js'; + +const { DataTypes } = Sequelize; + +export default class PersistentEOSIDtoSteamID extends BasePlugin { + static get description() { + return "Stores into a DB every association of SteamID-EOSID"; + } + + static get defaultEnabled() { + return false; + } + + static get optionsSpecification() { + return { + database: { + required: true, + connector: 'sequelize', + description: 'The Sequelize connector.', + default: 'sqlite' + } + }; + } + + constructor(server, options, connectors) { + super(server, options, connectors); + + this.models = {}; + + this.createModel( + 'SteamIDtoEOSID', + { + steamID: { + type: DataTypes.STRING, + primaryKey: true + }, + eosID: { + type: DataTypes.STRING, + } + }, + { + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_ci' + } + ); + + this.onPlayerConnected = this.onPlayerConnected.bind(this); + } + + createModel(name, schema) { + this.models[ name ] = this.options.database.define(`EOS_${name}`, schema, { + timestamps: false, + indexes: [ + { + unique: true, + fields: [ 'eosID' ] + }, + ] + }); + } + + async prepareToMount() { + await this.models.SteamIDtoEOSID.sync(); + } + + async mount() { + this.server.on('PLAYER_CONNECTED', this.onPlayerConnected); + this.verbose(1, 'Mounted') + } + + async unmount() { + this.server.removeEventListener('PLAYER_CONNECTED', this.onPlayerConnected); + } + + async onPlayerConnected(info) { + await this.models.SteamIDtoEOSID.upsert({ + steamID: info.player.steamID, + eosID: info.eosID + }); + } + + async getByEOSID(eosID) { + return await this.models.SteamIDtoEOSID.findOne({ where: { eosID: eosID } }) + } + + async getBySteamID(steamID) { + return await this.models.SteamIDtoEOSID.findOne({ where: { steamID: steamID } }) + } +} From b4d87d7be4ded54f6de2603333c58b65f8dd557a Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Mon, 6 Nov 2023 23:10:49 +0100 Subject: [PATCH 06/21] chore: removed console.log --- core/rcon.js | 1 - 1 file changed, 1 deletion(-) diff --git a/core/rcon.js b/core/rcon.js index f299c01..9799b8e 100644 --- a/core/rcon.js +++ b/core/rcon.js @@ -129,7 +129,6 @@ export default class Rcon extends EventEmitter { return buffer; } #onData(data) { - console.log(data) Logger.verbose("RCON", 4, `Got data: ${this.#bufToHexString(data)}`); this.stream = Buffer.concat([this.stream, data], this.stream.byteLength + data.byteLength); while (this.stream.byteLength >= 7) { From 5427d360187d974b7fd1b4d9691082564daeca63 Mon Sep 17 00:00:00 2001 From: vohk <8020903+vohk@users.noreply.github.com> Date: Fri, 17 Nov 2023 22:40:14 -0800 Subject: [PATCH 07/21] Update auto-kick-unassigned.js Fixed typo in unassigned warning. --- squad-server/plugins/auto-kick-unassigned.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squad-server/plugins/auto-kick-unassigned.js b/squad-server/plugins/auto-kick-unassigned.js index 8a8d784..1a4c81b 100644 --- a/squad-server/plugins/auto-kick-unassigned.js +++ b/squad-server/plugins/auto-kick-unassigned.js @@ -17,7 +17,7 @@ export default class AutoKickUnassigned extends BasePlugin { warningMessage: { required: false, description: 'Message SquadJS will send to players warning them they will be kicked', - default: 'Join a squad, you are are unassigned and will be kicked' + default: 'Join a squad, you are unassigned and will be kicked' }, kickMessage: { required: false, From 685248df565e3d08d13dc103f48f6885593b52ea Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Sat, 18 Nov 2023 23:31:34 +0100 Subject: [PATCH 08/21] chore: rcon server messages to file + convert steamids to eosid in rcon->execute() method --- core/rcon.js | 83 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/core/rcon.js b/core/rcon.js index 9799b8e..fdaa058 100644 --- a/core/rcon.js +++ b/core/rcon.js @@ -1,10 +1,13 @@ import { EventEmitter } from "node:events"; import net from "node:net"; import Logger from "./logger.js"; +import fs from 'fs'; +import path from 'path'; +const RCON_LOG_FILEPATH = "RCON_RECEIVED_MESSAGES.log" export default class Rcon extends EventEmitter { constructor(options = {}) { super(); - for (const option of ["host", "port", "password"]) if (!(option in options)) throw new Error(`${option} must be specified.`); + for (const option of [ "host", "port", "password" ]) if (!(option in options)) throw new Error(`${option} must be specified.`); this.host = options.host; this.port = options.port; this.password = options.password; @@ -26,13 +29,15 @@ export default class Rcon extends EventEmitter { this.passThroughTimeOut = options.passThroughTimeOut || 60000; this.passThroughMaxClients = 1; //options.passThroughMaxClients || 10; this.passThroughChallenge = options.passThroughChallenge || options.password; + this.dumpRconResponsesToFile = options.dumpRconResponsesToFile || false; this.rconClients = {}; - for (let i = 1; i <= this.passThroughMaxClients; i++) this.rconClients[`${i}`] = null; + for (let i = 1; i <= this.passThroughMaxClients; i++) this.rconClients[ `${i}` ] = null; this.ptServer = null; this.steamIndex = { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; // example dtata this.eosIndex = { "00026e21ce3d43c792613bdbb6dec1ba": "76561198799344716" }; // example dtata + this.rotateLogFile(RCON_LOG_FILEPATH) } processChatPacket(decodedPacket) { @@ -79,6 +84,12 @@ export default class Rcon extends EventEmitter { }); } async execute(body) { + let steamID = body.match(/\d{17}/); + if (steamID) { + steamID = steamID[ 0 ] + body = body.replace(/\d{17}/, this.steamIndex[ steamID ]); + } + return new Promise((resolve, reject) => { if (!this.connected) return reject(new Error("Rcon not connected.")); if (!this.client.writable) return reject(new Error("Unable to write to node:net socket")); @@ -95,7 +106,7 @@ export default class Rcon extends EventEmitter { this.removeListener(listenerId, outputData); return reject(new Error(`Rcon response timed out`)); }; - if (this.msgId > this.msgIdHigh -2) this.msgId = this.msgIdLow; + if (this.msgId > this.msgIdHigh - 2) this.msgId = this.msgIdLow; const listenerId = `response${this.msgId}`; const timeOut = setTimeout(timedOut, 10000); this.once(listenerId, outputData); @@ -130,12 +141,13 @@ export default class Rcon extends EventEmitter { } #onData(data) { Logger.verbose("RCON", 4, `Got data: ${this.#bufToHexString(data)}`); - this.stream = Buffer.concat([this.stream, data], this.stream.byteLength + data.byteLength); + this.stream = Buffer.concat([ this.stream, data ], this.stream.byteLength + data.byteLength); while (this.stream.byteLength >= 7) { const packet = this.#decode(); if (!packet) break; else Logger.verbose("RCON", 3, `Processing decoded packet: Size: ${packet.size}, ID: ${packet.id}, Type: ${packet.type}, Body: ${packet.body}`); - + this.appendToFile(RCON_LOG_FILEPATH, packet.body) + if (packet.id > this.msgIdHigh) this.emit(`responseForward_1`, packet); else if (packet.type === this.type.response) this.#onResponse(packet); else if (packet.type === this.type.server) this.#onServer(packet); @@ -145,12 +157,12 @@ export default class Rcon extends EventEmitter { #onServer(packet) { this.emit("server", packet); for (const client in this.rconClients) - if (this.rconClients[client]) { - this.emit(`serverForward_${this.rconClients[client].rconIdClient}`, packet.body); + if (this.rconClients[ client ]) { + this.emit(`serverForward_${this.rconClients[ client ].rconIdClient}`, packet.body); } } #decode() { - if (this.stream[0] === 0 && this.stream[1] === 1 && this.stream[2] === 0 && this.stream[3] === 0 && this.stream[4] === 0 && this.stream[5] === 0 && this.stream[6] === 0) { + if (this.stream[ 0 ] === 0 && this.stream[ 1 ] === 1 && this.stream[ 2 ] === 0 && this.stream[ 3 ] === 0 && this.stream[ 4 ] === 0 && this.stream[ 5 ] === 0 && this.stream[ 6 ] === 0) { this.stream = this.stream.subarray(7); return this.soh; } @@ -159,11 +171,11 @@ export default class Rcon extends EventEmitter { else if (bufSize <= this.stream.byteLength - 4 && this.stream.byteLength >= 12) { const bufId = this.stream.readInt32LE(4); const bufType = this.stream.readInt32LE(8); - if (this.stream[bufSize + 2] !== 0 || this.stream[bufSize + 3] !== 0 || bufId < 0 || bufType < 0 || bufType > 5) return this.#badPacket(); + if (this.stream[ bufSize + 2 ] !== 0 || this.stream[ bufSize + 3 ] !== 0 || bufId < 0 || bufType < 0 || bufType > 5) return this.#badPacket(); else { const response = { size: bufSize, id: bufId, type: bufType, body: this.stream.toString("utf8", 12, bufSize + 2) }; this.stream = this.stream.subarray(bufSize + 4); - if (response.body === "" && this.stream[0] === 0 && this.stream[1] === 1 && this.stream[2] === 0 && this.stream[3] === 0 && this.stream[4] === 0 && this.stream[5] === 0 && this.stream[6] === 0) { + if (response.body === "" && this.stream[ 0 ] === 0 && this.stream[ 1 ] === 1 && this.stream[ 2 ] === 0 && this.stream[ 3 ] === 0 && this.stream[ 4 ] === 0 && this.stream[ 5 ] === 0 && this.stream[ 6 ] === 0) { this.stream = this.stream.subarray(7); response.body = ""; } @@ -213,7 +225,7 @@ export default class Rcon extends EventEmitter { this.ptServer.listen(this.passThroughPort, () => Logger.verbose("RCON", 1, `Pass-through Server: Listening on port ${this.passThroughPort}`)); } closeServer() { - for (const client in this.rconClients) if (this.rconClients[client]) this.rconClients[client].end(); + for (const client in this.rconClients) if (this.rconClients[ client ]) this.rconClients[ client ].end(); if (!this.ptServer) return; this.ptServer.close(() => this.#onServerClose()); } @@ -239,7 +251,7 @@ export default class Rcon extends EventEmitter { if (!client.rconIdClient) return; this.removeAllListeners(`serverForward_${client.rconIdClient}`); this.removeAllListeners(`responseForward_${client.rconIdClient}`); - this.rconClients[`${client.rconIdClient}`] = null; + this.rconClients[ `${client.rconIdClient}` ] = null; Logger.verbose("RCON", 1, `Pass-through Server: Client-${client.rconIdClient} Disconnected`); } #onClientTimeOut(client) { @@ -248,7 +260,7 @@ export default class Rcon extends EventEmitter { } #onClientData(client, data) { if (!client.rconStream) client.rconStream = new Buffer.alloc(0); - client.rconStream = Buffer.concat([client.rconStream, data], client.rconStream.byteLength + data.byteLength); + client.rconStream = Buffer.concat([ client.rconStream, data ], client.rconStream.byteLength + data.byteLength); while (client.rconStream.byteLength >= 4) { const packet = this.#decodeClient(client); if (!packet) break; @@ -258,7 +270,7 @@ export default class Rcon extends EventEmitter { if (!client.rconWheel || client.rconWheel > 20) client.rconWheel = 0; else client.rconWheel++; - client.rconIdQueueNEW[`${client.rconWheel}`] = packet.id + client.rconIdQueueNEW[ `${client.rconWheel}` ] = packet.id const encoded = this.#encode(packet.type, this.specialId + client.rconWheel, this.#steamToEosClient(packet.body)); //////////////////////////////////////////////// this.client.write(encoded.toString("binary"), "binary"); @@ -287,9 +299,9 @@ export default class Rcon extends EventEmitter { client.rconHasAuthed = true; client.rconIdQueueNEW = {} for (let i = 1; i <= this.passThroughMaxClients; i++) { - if (this.rconClients[`${i}`] === null) { + if (this.rconClients[ `${i}` ] === null) { client.rconIdClient = i; - this.rconClients[`${i}`] = client; + this.rconClients[ `${i}` ] = client; break; } } @@ -307,11 +319,11 @@ export default class Rcon extends EventEmitter { //console.log(client.rconIdQueueNEW);////////////////////////////////////////////////////////////////////////////////////////// - client.write(this.#encode(packet.type, client.rconIdQueueNEW[int], this.#eosToSteam(packet.body)).toString("binary"), "binary"); + client.write(this.#encode(packet.type, client.rconIdQueueNEW[ int ], this.#eosToSteam(packet.body)).toString("binary"), "binary"); } else if (packet.body != "") { const int = packet.id - this.specialId - client.write(this.#encode(0, client.rconIdQueueNEW[int]).toString("binary"), "binary"); - client.write(this.#encodeSpecial(client.rconIdQueueNEW[int]).toString("binary"), "binary"); + client.write(this.#encode(0, client.rconIdQueueNEW[ int ]).toString("binary"), "binary"); + client.write(this.#encodeSpecial(client.rconIdQueueNEW[ int ]).toString("binary"), "binary"); } } #encodeSpecial(id) { @@ -336,8 +348,8 @@ export default class Rcon extends EventEmitter { } addIds(steamId, eosId) { - this.steamIndex[steamId] = eosId; // { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; - this.eosIndex[eosId] = steamId; + this.steamIndex[ steamId ] = eosId; // { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; + this.eosIndex[ eosId ] = steamId; } removeIds(eosId) { @@ -347,7 +359,7 @@ export default class Rcon extends EventEmitter { #steamToEosClient(body) { //assume client does not send more than 1 steamId per msg const m = body.match(/[0-9]{17}/); - if (m && m[1] in this.steamIndex) return body.replaceAll(`${m[0]}`, this.steamIndex[m[0]]); + if (m && m[ 1 ] in this.steamIndex) return body.replaceAll(`${m[ 0 ]}`, this.steamIndex[ m[ 0 ] ]); return body; } @@ -363,11 +375,36 @@ export default class Rcon extends EventEmitter { console.warn(line); for (const r of this.defs) { const match = line.match(r.regex); - if (match && match.groups.eosId in this.eosIndex) return r.rep(line, this.eosIndex[match.groups.eosId], match.groups.eosId); + if (match && match.groups.eosId in this.eosIndex) return r.rep(line, this.eosIndex[ match.groups.eosId ], match.groups.eosId); } return line; } + appendToFile(filePath, content) { + if (!this.dumpRconResponsesToFile) return; + const dir = path.dirname(filePath); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.appendFile(filePath, content + "\n", (err) => { + if (err) throw err; + }); + } + rotateLogFile(logFile) { + if (!this.dumpRconResponsesToFile) return; + if (fs.existsSync(logFile)) { + const ext = path.extname(logFile); + const base = path.basename(logFile, ext); + const dir = path.dirname(logFile); + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const newFile = path.join(dir, `${base}_${timestamp}${ext}`); + + fs.renameSync(logFile, newFile); + } + } + defs = [ //strict matching to avoid 'name as steamId errors' { From 522b85cdcc4aeba741c0682bd81e5aad995cbed6 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Sat, 18 Nov 2023 23:33:03 +0100 Subject: [PATCH 09/21] fix+chore: improved server information parsing + fixed server public slots count --- squad-server/index.js | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/squad-server/index.js b/squad-server/index.js index e350651..3685463 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -76,7 +76,7 @@ export default class SquadServer extends EventEmitter { await this.logParser.watch(); await this.updateSquadList(); - await this.updatePlayerList(); + await this.updatePlayerList(this); await this.updateLayerInformation(); await this.updateA2SInformation(); @@ -96,12 +96,13 @@ export default class SquadServer extends EventEmitter { port: this.options.rconPort, password: this.options.rconPassword, autoReconnectInterval: this.options.rconAutoReconnectInterval, + dumpRconResponsesToFile: this.options.dumpRconResponsesToFile, passThroughPort: this.options.rconPassThroughPort, passThrough: this.options.rconPassThrough }); this.rcon.on('CHAT_MESSAGE', async (data) => { - data.player = await this.getPlayerByEOSID(data.steamID); + data.player = await this.getPlayerBySteamID(data.steamID); this.emit('CHAT_MESSAGE', data); const command = data.message.match(/!([^ ]+) ?(.*)/); @@ -113,7 +114,7 @@ export default class SquadServer extends EventEmitter { }); this.rcon.on('POSSESSED_ADMIN_CAMERA', async (data) => { - data.player = await this.getPlayerByEOSID(data.steamID); + data.player = await this.getPlayerBySteamID(data.steamID); this.adminsInAdminCam[ data.steamID ] = data.time; @@ -121,7 +122,7 @@ export default class SquadServer extends EventEmitter { }); this.rcon.on('UNPOSSESSED_ADMIN_CAMERA', async (data) => { - data.player = await this.getPlayerByEOSID(data.steamID); + data.player = await this.getPlayerBySteamID(data.steamID); if (this.adminsInAdminCam[ data.steamID ]) { data.duration = data.time.getTime() - this.adminsInAdminCam[ data.steamID ].getTime(); } else { @@ -144,19 +145,19 @@ export default class SquadServer extends EventEmitter { }); this.rcon.on('PLAYER_KICKED', async (data) => { - data.player = await this.getPlayerByEOSID(data.steamID); + data.player = await this.getPlayerBySteamID(data.steamID); this.emit('PLAYER_KICKED', data); }); this.rcon.on('PLAYER_BANNED', async (data) => { - data.player = await this.getPlayerByEOSID(data.steamID); + data.player = await this.getPlayerBySteamID(data.steamID); this.emit('PLAYER_BANNED', data); }); this.rcon.on('SQUAD_CREATED', async (data) => { - data.player = await this.getPlayerByEOSID(data.playerSteamID, true); + data.player = await this.getPlayerBySteamID(data.playerSteamID, true); delete data.playerName; delete data.playerSteamID; @@ -213,7 +214,7 @@ export default class SquadServer extends EventEmitter { this.rcon.addIds(data.steamID, data.eosID) - data.player = await this.getPlayerByEOSID(data.steamID); + data.player = await this.getPlayerBySteamID(data.steamID); if (data.player) data.player.suffix = data.playerSuffix; delete data.steamID; @@ -223,7 +224,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_DISCONNECTED', async (data) => { - data.player = await this.getPlayerByEOSID(data.steamID); + data.player = await this.getPlayerBySteamID(data.steamID); delete data.steamID; @@ -379,8 +380,8 @@ export default class SquadServer extends EventEmitter { players.push({ ...oldPlayerInfo[ player.steamID ], ...player, - playercontroller: this.logParser.eventStore.players[player.steamID] - ? this.logParser.eventStore.players[player.steamID].controller + playercontroller: this.logParser.eventStore.players[ player.steamID ] + ? this.logParser.eventStore.players[ player.steamID ].controller : null, squad: await this.getSquadByID(player.teamID, player.squadID) }); @@ -484,16 +485,22 @@ export default class SquadServer extends EventEmitter { const info = { raw: data, - serverName: data.name, + serverName: data.ServerName_s, maxPlayers: parseInt(data.MaxPlayers), - publicSlots: parseInt(data.PublicQueueLimit_I), + publicQueueLimit: parseInt(data.PublicQueueLimit_I), reserveSlots: parseInt(data.PlayerReserveCount_I), - a2sPlayerCount: parseInt(data.PlayerCount_I), + playerCount: parseInt(data.PlayerCount_I), publicQueue: parseInt(data.PublicQueue_I), reserveQueue: parseInt(data.ReservedQueue_I), + currentLayer: data.MapName_s, + nextLayer: data.NextLayer_s, + + teamOne: data.TeamOne_s.replace(new RegExp(data.MapName_s, "i"), ''), + teamTwo: data.TeamTwo_s.replace(new RegExp(data.MapName_s, "i"), ''), + matchTimeout: parseFloat(data.MatchTimeout_d), gameVersion: data.GameVersion_s }; @@ -501,10 +508,10 @@ export default class SquadServer extends EventEmitter { this.serverName = info.serverName; this.maxPlayers = info.maxPlayers; - this.publicSlots = info.publicSlots; + this.publicSlots = info.maxPlayers - info.reserveSlots; this.reserveSlots = info.reserveSlots; - this.a2sPlayerCount = info.a2sPlayerCount; + this.a2sPlayerCount = info.playerCount; this.publicQueue = info.publicQueue; this.reserveQueue = info.reserveQueue; @@ -512,6 +519,7 @@ export default class SquadServer extends EventEmitter { this.gameVersion = info.gameVersion; this.emit('UPDATED_A2S_INFORMATION', info); + this.emit('UPDATED_SERVER_INFORMATION', info); } catch (err) { Logger.verbose('SquadServer', 1, 'Failed to update A2S information.', err); } From 82dcbd49a67def9f163e8c55952b1dbbcd480b90 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Sat, 18 Nov 2023 23:34:04 +0100 Subject: [PATCH 10/21] feat: support Squad V7 rcon messages --- squad-server/rcon.js | 109 ++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/squad-server/rcon.js b/squad-server/rcon.js index dcef0af..9ad8321 100644 --- a/squad-server/rcon.js +++ b/squad-server/rcon.js @@ -5,17 +5,18 @@ import PersistentEOSIDtoSteamID from './plugins/persistent-eosid-to-steamid.js'; export default class SquadRcon extends Rcon { processChatPacket(decodedPacket) { const matchChat = decodedPacket.body.match( - /\[(ChatAll|ChatTeam|ChatSquad|ChatAdmin)] \[SteamID:([0-9a-f]{32})] (.+?) : (.*)/ + /\[(ChatAll|ChatTeam|ChatSquad|ChatAdmin)] \[Online IDs:EOS: ([0-9a-f]{32}) steam: (\d{17})\] (.+?) : (.*)/ ); if (matchChat) { Logger.verbose('SquadRcon', 2, `Matched chat message: ${decodedPacket.body}`); this.emit('CHAT_MESSAGE', { raw: decodedPacket.body, - chat: matchChat[1], - steamID: matchChat[2], - name: matchChat[3], - message: matchChat[4], + chat: matchChat[ 1 ], + eosID: matchChat[ 2 ], + steamID: matchChat[ 3 ], + name: matchChat[ 4 ], + message: matchChat[ 5 ], time: new Date() }); @@ -23,14 +24,14 @@ export default class SquadRcon extends Rcon { } const matchPossessedAdminCam = decodedPacket.body.match( - /\[SteamID:([0-9a-f]{32})] (.+?) has possessed admin camera./ + /\[Online Ids:EOS: ([0-9a-f]{32}) steam: (\d{17})\] (.+) has possessed admin camera\./ ); if (matchPossessedAdminCam) { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); this.emit('POSSESSED_ADMIN_CAMERA', { raw: decodedPacket.body, - steamID: matchPossessedAdminCam[1], - name: matchPossessedAdminCam[2], + steamID: matchPossessedAdminCam[ 2 ], + name: matchPossessedAdminCam[ 3 ], time: new Date() }); @@ -38,14 +39,14 @@ export default class SquadRcon extends Rcon { } const matchUnpossessedAdminCam = decodedPacket.body.match( - /\[SteamID:([0-9a-f]{32})] (.+?) has unpossessed admin camera./ + /\[Online IDs:EOS: ([0-9a-f]{32}) steam: (\d{17})\] (.+) has unpossessed admin camera\./ ); if (matchUnpossessedAdminCam) { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); this.emit('UNPOSSESSED_ADMIN_CAMERA', { raw: decodedPacket.body, - steamID: matchUnpossessedAdminCam[1], - name: matchUnpossessedAdminCam[2], + steamID: matchPossessedAdminCam[ 2 ], + name: matchPossessedAdminCam[ 3 ], time: new Date() }); @@ -60,8 +61,8 @@ export default class SquadRcon extends Rcon { this.emit('PLAYER_WARNED', { raw: decodedPacket.body, - name: matchWarn[1], - reason: matchWarn[2], + name: matchWarn[ 1 ], + reason: matchWarn[ 2 ], time: new Date() }); @@ -69,16 +70,16 @@ export default class SquadRcon extends Rcon { } const matchKick = decodedPacket.body.match( - /Kicked player ([0-9]+)\. \[steamid=([0-9a-f]{32})] (.*)/ + /Kicked player ([0-9]+)\. \[Online IDs= EOS: ([0-9a-f]{32}) steam: (\d{17})] (.*)/ ); if (matchKick) { Logger.verbose('SquadRcon', 2, `Matched kick message: ${decodedPacket.body}`); this.emit('PLAYER_KICKED', { raw: decodedPacket.body, - playerID: matchKick[1], - steamID: matchKick[2], - name: matchKick[3], + playerID: matchKick[ 1 ], + steamID: matchKick[ 3 ], + name: matchKick[ 4 ], time: new Date() }); @@ -86,18 +87,18 @@ export default class SquadRcon extends Rcon { } const matchSqCreated = decodedPacket.body.match( - /(.+) \(Steam ID: ([0-9a-f]{32})\) has created Squad (\d+) \(Squad Name: (.+)\) on (.+)/ + /(.+) \(Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17})\) has created Squad (\d+) \(Squad Name: (.+)\) on (.+)/ ); if (matchSqCreated) { Logger.verbose('SquadRcon', 2, `Matched Squad Created: ${decodedPacket.body}`); this.emit('SQUAD_CREATED', { time: new Date(), - playerName: matchSqCreated[1], - playerSteamID: matchSqCreated[2], - squadID: matchSqCreated[3], - squadName: matchSqCreated[4], - teamName: matchSqCreated[5] + playerName: matchSqCreated[ 1 ], + playerSteamID: matchSqCreated[ 3 ], + squadID: matchSqCreated[ 4 ], + squadName: matchSqCreated[ 5 ], + teamName: matchSqCreated[ 6 ] }); return; @@ -111,10 +112,10 @@ export default class SquadRcon extends Rcon { this.emit('PLAYER_BANNED', { raw: decodedPacket.body, - playerID: matchBan[1], - steamID: matchBan[2], - name: matchBan[3], - interval: matchBan[4], + playerID: matchBan[ 1 ], + steamID: matchBan[ 2 ], + name: matchBan[ 3 ], + interval: matchBan[ 4 ], time: new Date() }); } @@ -123,15 +124,15 @@ export default class SquadRcon extends Rcon { async getCurrentMap() { const response = await this.execute('ShowCurrentMap'); const match = response.match(/^Current level is (.*), layer is (.*)/); - return { level: match[1], layer: match[2] }; + return { level: match[ 1 ], layer: match[ 2 ] }; } async getNextMap() { const response = await this.execute('ShowNextMap'); const match = response.match(/^Next level is (.*), layer is (.*)/); return { - level: match[1] !== '' ? match[1] : null, - layer: match[2] !== 'To be voted' ? match[2] : null + level: match[ 1 ] !== '' ? match[ 1 ] : null, + layer: match[ 2 ] !== 'To be voted' ? match[ 2 ] : null }; } @@ -140,32 +141,24 @@ export default class SquadRcon extends Rcon { const players = []; - if(!response || response.length < 1) return players; + if (!response || response.length < 1) return players; for (const line of response.split('\n')) { const match = line.match( - /ID: ([0-9]+) \| SteamID: ([0-9a-f]{32}) \| Name: (.+) \| Team ID: ([0-9]+) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False) \| Role: ([A-Za-z0-9_]*)\b/ + /ID: ([0-9]+) \| Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17}) \| Name: (.+) \| Team ID: ([0-9]+) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False) \| Role: ([A-Za-z0-9_]*)\b/ ); if (!match) continue; - let steamID = this.eosIndex[ match[ 2 ] ] - - const persEosIdPlugin = server.plugins.find(p => p instanceof PersistentEOSIDtoSteamID) - if (persEosIdPlugin && !this.eosIndex[ match[ 2 ] ]) { - steamID = (await persEosIdPlugin.getByEOSID(match[ 2 ])).steamID - this.addIds(steamID, match[ 2 ]); - Logger.verbose("RCON", 1, `Getting SteamID for player: ${match[ 3 ]} from EOSID: ${match[ 2 ]} => ${steamID}`) - } - + server.rcon.addIds(match[ 3 ], match[ 2 ]); players.push({ - playerID: match[1], - steamID: steamID, - EOSID: match[2], - name: match[3], - teamID: match[4], - squadID: match[5] !== 'N/A' ? match[5] : null, - isLeader: match[6] === 'True', - role: match[7] + playerID: match[ 1 ], + EOSID: match[ 2 ], + steamID: match[ 3 ], + name: match[ 4 ], + teamID: match[ 5 ], + squadID: match[ 6 ] !== 'N/A' ? match[ 5 ] : null, + isLeader: match[ 7 ] === 'True', + role: match[ 8 ] }); } @@ -181,21 +174,21 @@ export default class SquadRcon extends Rcon { for (const line of responseSquad.split('\n')) { const match = line.match( - /ID: ([0-9]+) \| Name: (.+) \| Size: ([0-9]+) \| Locked: (True|False) \| Creator Name: (.+) \| Creator Steam ID: ([0-9a-f]{32})/ + /ID: ([0-9]+) \| Name: (.+) \| Size: ([0-9]+) \| Locked: (True|False) \| Creator Name: (.+) \| Creator Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17})/ ); const matchSide = line.match(/Team ID: (1|2) \((.+)\)/); if (matchSide) { - teamID = matchSide[1]; - teamName = matchSide[2]; + teamID = matchSide[ 1 ]; + teamName = matchSide[ 2 ]; } if (!match) continue; squads.push({ - squadID: match[1], - squadName: match[2], - size: match[3], - locked: match[4], - creatorName: match[5], - creatorSteamID: match[6], + squadID: match[ 1 ], + squadName: match[ 2 ], + size: match[ 3 ], + locked: match[ 4 ], + creatorName: match[ 5 ], + creatorSteamID: match[ 7 ], teamID: teamID, teamName: teamName }); From 6a9b691bcd0aed2dd89c47e5e88cfed7023391e0 Mon Sep 17 00:00:00 2001 From: werewolfboy13 Date: Sat, 18 Nov 2023 17:40:18 -0600 Subject: [PATCH 11/21] Adds Support for #322 --- README.md | 2 +- config.json | 2 +- .../log-parser/player-disconnected.js | 22 +++++++++---------- squad-server/rcon.js | 6 ++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e1b720e..b137e4f 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Interested in creating your own plugin? [See more here](./squad-server/plugins/r
Description

Message SquadJS will send to players warning them they will be kicked

Default
-
Join a squad, you are are unassigned and will be kicked
+
Join a squad, you are unassigned and will be kicked
  • kickMessage

    Description

    Message to send to players when they are kicked

    diff --git a/config.json b/config.json index e68f5e5..c3e82bc 100644 --- a/config.json +++ b/config.json @@ -43,7 +43,7 @@ { "plugin": "AutoKickUnassigned", "enabled": true, - "warningMessage": "Join a squad, you are are unassigned and will be kicked", + "warningMessage": "Join a squad, you are unassigned and will be kicked", "kickMessage": "Unassigned - automatically removed", "frequencyOfWarnings": 30, "unassignedTimer": 360, diff --git a/squad-server/log-parser/player-disconnected.js b/squad-server/log-parser/player-disconnected.js index 0997697..352a012 100644 --- a/squad-server/log-parser/player-disconnected.js +++ b/squad-server/log-parser/player-disconnected.js @@ -1,16 +1,16 @@ export default { regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UChannel::Close: Sending CloseBunch\. ChIndex == [0-9]+\. Name: \[UChannel\] ChIndex: [0-9]+, Closing: [0-9]+ \[UNetConnection\] RemoteAddr: ([0-9]{17}):[0-9]+, Name: SteamNetConnection_[0-9]+, Driver: GameNetDriver SteamNetDriver_[0-9]+, IsServer: YES, PC: ([^ ]+PlayerController_C_[0-9]+), Owner: [^ ]+PlayerController_C_[0-9]+/, - onMatch: (args, logParser) => { - const data = { - raw: args[0], - time: args[1], - chainID: args[2], - steamID: args[3], - playerController: args[4] - }; + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: args[2], + steamID: args[3], + playerController: args[4] + }; - logParser.eventStore.disconnected[data.steamID] = true; - logParser.emit('PLAYER_DISCONNECTED', data); - } + logParser.eventStore.disconnected[data.steamID] = true; + logParser.emit('PLAYER_DISCONNECTED', data); + } }; diff --git a/squad-server/rcon.js b/squad-server/rcon.js index b618ba4..7209d7a 100644 --- a/squad-server/rcon.js +++ b/squad-server/rcon.js @@ -139,8 +139,8 @@ export default class SquadRcon extends Rcon { const players = []; - if(!response || response.length < 1) return players; - + if (!response || response.length < 1) return players; + for (const line of response.split('\n')) { const match = line.match( /ID: ([0-9]+) \| SteamID: ([0-9]{17}) \| Name: (.+) \| Team ID: ([0-9]+) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False) \| Role: ([A-Za-z0-9_]*)\b/ @@ -168,7 +168,7 @@ export default class SquadRcon extends Rcon { let teamName; let teamID; - if(!responseSquad || responseSquad.length < 1) return squads; + if (!responseSquad || responseSquad.length < 1) return squads; for (const line of responseSquad.split('\n')) { const match = line.match( From 297ec0dc4597ddd070d3495e9a9e06e2bd678c29 Mon Sep 17 00:00:00 2001 From: Marek Date: Sat, 18 Nov 2023 17:56:23 -0600 Subject: [PATCH 12/21] Update package.json to 3.8.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ec1b126..29459d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "SquadJS", - "version": "3.8.1", + "version": "3.8.2", "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", "author": "Thomas Smyth ", "license": "BSL-1.0", From 4c7f55339ae256e02d38e2b31e09dbd59d50939a Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 02:48:36 +0100 Subject: [PATCH 13/21] fix: typo matchUnpossessedAdminCam and removed unused dependency --- README.md | 12 ++++++ config.json | 12 +++--- squad-server/rcon.js | 87 ++++++++++++++++++++++---------------------- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index e1b720e..ae1cdfe 100644 --- a/README.md +++ b/README.md @@ -810,6 +810,18 @@ Grafana:
    300000
  • +
    + PersistentEOSIDtoSteamID +

    PersistentEOSIDtoSteamID

    +

    Stores into a DB every association of SteamID-EOSID

    +

    Options

    +
    • database (Required)

      +
      Description
      +

      The Sequelize connector.

      +
      Default
      +
      sqlite
    +
    +
    SeedingMode

    SeedingMode

    diff --git a/config.json b/config.json index b707e35..30ef9e2 100644 --- a/config.json +++ b/config.json @@ -5,8 +5,6 @@ "queryPort": 27165, "rconPort": 21114, "rconPassword": "password", - "rconPassThrough": true, - "rconPassThroughPort": 8124, "logReaderMode": "tail", "logDir": "C:/path/to/squad/log/folder", "ftp": { @@ -42,11 +40,6 @@ "sqlite": "sqlite:database.sqlite" }, "plugins": [ - { - "plugin": "PersistentEOSIDtoSteamID", - "enabled": true, - "database": "sqlite" - }, { "plugin": "AutoKickUnassigned", "enabled": true, @@ -224,6 +217,11 @@ "broadcasts": [], "interval": 300000 }, + { + "plugin": "PersistentEOSIDtoSteamID", + "enabled": false, + "database": "sqlite" + }, { "plugin": "SeedingMode", "enabled": true, diff --git a/squad-server/rcon.js b/squad-server/rcon.js index 9ad8321..3953038 100644 --- a/squad-server/rcon.js +++ b/squad-server/rcon.js @@ -1,6 +1,5 @@ import Logger from 'core/logger'; import Rcon from 'core/rcon'; -import PersistentEOSIDtoSteamID from './plugins/persistent-eosid-to-steamid.js'; export default class SquadRcon extends Rcon { processChatPacket(decodedPacket) { @@ -12,11 +11,11 @@ export default class SquadRcon extends Rcon { this.emit('CHAT_MESSAGE', { raw: decodedPacket.body, - chat: matchChat[ 1 ], - eosID: matchChat[ 2 ], - steamID: matchChat[ 3 ], - name: matchChat[ 4 ], - message: matchChat[ 5 ], + chat: matchChat[1], + eosID: matchChat[2], + steamID: matchChat[3], + name: matchChat[4], + message: matchChat[5], time: new Date() }); @@ -30,8 +29,8 @@ export default class SquadRcon extends Rcon { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); this.emit('POSSESSED_ADMIN_CAMERA', { raw: decodedPacket.body, - steamID: matchPossessedAdminCam[ 2 ], - name: matchPossessedAdminCam[ 3 ], + steamID: matchPossessedAdminCam[2], + name: matchPossessedAdminCam[3], time: new Date() }); @@ -45,8 +44,8 @@ export default class SquadRcon extends Rcon { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); this.emit('UNPOSSESSED_ADMIN_CAMERA', { raw: decodedPacket.body, - steamID: matchPossessedAdminCam[ 2 ], - name: matchPossessedAdminCam[ 3 ], + steamID: matchUnpossessedAdminCam[2], + name: matchUnpossessedAdminCam[3], time: new Date() }); @@ -61,8 +60,8 @@ export default class SquadRcon extends Rcon { this.emit('PLAYER_WARNED', { raw: decodedPacket.body, - name: matchWarn[ 1 ], - reason: matchWarn[ 2 ], + name: matchWarn[1], + reason: matchWarn[2], time: new Date() }); @@ -77,9 +76,9 @@ export default class SquadRcon extends Rcon { this.emit('PLAYER_KICKED', { raw: decodedPacket.body, - playerID: matchKick[ 1 ], - steamID: matchKick[ 3 ], - name: matchKick[ 4 ], + playerID: matchKick[1], + steamID: matchKick[3], + name: matchKick[4], time: new Date() }); @@ -94,11 +93,11 @@ export default class SquadRcon extends Rcon { this.emit('SQUAD_CREATED', { time: new Date(), - playerName: matchSqCreated[ 1 ], - playerSteamID: matchSqCreated[ 3 ], - squadID: matchSqCreated[ 4 ], - squadName: matchSqCreated[ 5 ], - teamName: matchSqCreated[ 6 ] + playerName: matchSqCreated[1], + playerSteamID: matchSqCreated[3], + squadID: matchSqCreated[4], + squadName: matchSqCreated[5], + teamName: matchSqCreated[6] }); return; @@ -112,10 +111,10 @@ export default class SquadRcon extends Rcon { this.emit('PLAYER_BANNED', { raw: decodedPacket.body, - playerID: matchBan[ 1 ], - steamID: matchBan[ 2 ], - name: matchBan[ 3 ], - interval: matchBan[ 4 ], + playerID: matchBan[1], + steamID: matchBan[2], + name: matchBan[3], + interval: matchBan[4], time: new Date() }); } @@ -124,15 +123,15 @@ export default class SquadRcon extends Rcon { async getCurrentMap() { const response = await this.execute('ShowCurrentMap'); const match = response.match(/^Current level is (.*), layer is (.*)/); - return { level: match[ 1 ], layer: match[ 2 ] }; + return { level: match[1], layer: match[2] }; } async getNextMap() { const response = await this.execute('ShowNextMap'); const match = response.match(/^Next level is (.*), layer is (.*)/); return { - level: match[ 1 ] !== '' ? match[ 1 ] : null, - layer: match[ 2 ] !== 'To be voted' ? match[ 2 ] : null + level: match[1] !== '' ? match[1] : null, + layer: match[2] !== 'To be voted' ? match[2] : null }; } @@ -149,16 +148,16 @@ export default class SquadRcon extends Rcon { ); if (!match) continue; - server.rcon.addIds(match[ 3 ], match[ 2 ]); + server.rcon.addIds(match[3], match[2]); players.push({ - playerID: match[ 1 ], - EOSID: match[ 2 ], - steamID: match[ 3 ], - name: match[ 4 ], - teamID: match[ 5 ], - squadID: match[ 6 ] !== 'N/A' ? match[ 5 ] : null, - isLeader: match[ 7 ] === 'True', - role: match[ 8 ] + playerID: match[1], + EOSID: match[2], + steamID: match[3], + name: match[4], + teamID: match[5], + squadID: match[6] !== 'N/A' ? match[5] : null, + isLeader: match[7] === 'True', + role: match[8] }); } @@ -178,17 +177,17 @@ export default class SquadRcon extends Rcon { ); const matchSide = line.match(/Team ID: (1|2) \((.+)\)/); if (matchSide) { - teamID = matchSide[ 1 ]; - teamName = matchSide[ 2 ]; + teamID = matchSide[1]; + teamName = matchSide[2]; } if (!match) continue; squads.push({ - squadID: match[ 1 ], - squadName: match[ 2 ], - size: match[ 3 ], - locked: match[ 4 ], - creatorName: match[ 5 ], - creatorSteamID: match[ 7 ], + squadID: match[1], + squadName: match[2], + size: match[3], + locked: match[4], + creatorName: match[5], + creatorSteamID: match[7], teamID: teamID, teamName: teamName }); From f18ea1ab66c82b723065b0d78f5945710cfafcdb Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 02:50:06 +0100 Subject: [PATCH 14/21] chore: changes to make eslint happy --- core/rcon.js | 553 +++++++++++++++++++++++++++++---------------------- 1 file changed, 312 insertions(+), 241 deletions(-) diff --git a/core/rcon.js b/core/rcon.js index fdaa058..34cb185 100644 --- a/core/rcon.js +++ b/core/rcon.js @@ -1,21 +1,23 @@ -import { EventEmitter } from "node:events"; -import net from "node:net"; -import Logger from "./logger.js"; +/* eslint-disable */ +import { EventEmitter } from 'node:events'; +import net from 'node:net'; +import Logger from './logger.js'; import fs from 'fs'; import path from 'path'; -const RCON_LOG_FILEPATH = "RCON_RECEIVED_MESSAGES.log" +const RCON_LOG_FILEPATH = 'RCON_RECEIVED_MESSAGES.log'; export default class Rcon extends EventEmitter { constructor(options = {}) { super(); - for (const option of [ "host", "port", "password" ]) if (!(option in options)) throw new Error(`${option} must be specified.`); + for (const option of ['host', 'port', 'password']) + if (!(option in options)) throw new Error(`${option} must be specified.`); this.host = options.host; this.port = options.port; this.password = options.password; this.client = null; this.stream = new Buffer.alloc(0); this.type = { auth: 0x03, command: 0x02, response: 0x00, server: 0x01 }; - this.soh = { size: 7, id: 0, type: this.type.response, body: "" }; - this.responseString = { id: 0, body: "" }; + this.soh = { size: 7, id: 0, type: this.type.response, body: '' }; + this.responseString = { id: 0, body: '' }; this.connected = false; this.autoReconnect = false; this.autoReconnectDelay = options.autoReconnectDelay || 1000; @@ -31,78 +33,78 @@ export default class Rcon extends EventEmitter { this.passThroughChallenge = options.passThroughChallenge || options.password; this.dumpRconResponsesToFile = options.dumpRconResponsesToFile || false; this.rconClients = {}; - for (let i = 1; i <= this.passThroughMaxClients; i++) this.rconClients[ `${i}` ] = null; + for (let i = 1; i <= this.passThroughMaxClients; i++) this.rconClients[`${i}`] = null; this.ptServer = null; - this.steamIndex = { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; // example dtata - this.eosIndex = { "00026e21ce3d43c792613bdbb6dec1ba": "76561198799344716" }; // example dtata - - this.rotateLogFile(RCON_LOG_FILEPATH) + this.steamIndex = { '76561198799344716': '00026e21ce3d43c792613bdbb6dec1ba' }; // example dtata + this.eosIndex = { '00026e21ce3d43c792613bdbb6dec1ba': '76561198799344716' }; // example dtata + this.rotateLogFile(RCON_LOG_FILEPATH); } processChatPacket(decodedPacket) { console.log(decodedPacket.body); } // async connect() { return new Promise((resolve, reject) => { - if (this.client && this.connected && !this.client.destroyed) return reject(new Error("Rcon.connect() Rcon already connected.")); - this.removeAllListeners("server"); - this.removeAllListeners("auth"); - this.on("server", (pkt) => this.processChatPacket(pkt)); - this.once("auth", () => { - Logger.verbose("RCON", 1, `Connected to: ${this.host}:${this.port}`); + if (this.client && this.connected && !this.client.destroyed) + return reject(new Error('Rcon.connect() Rcon already connected.')); + this.removeAllListeners('server'); + this.removeAllListeners('auth'); + this.on('server', (pkt) => this.processChatPacket(pkt)); + this.once('auth', () => { + Logger.verbose('RCON', 1, `Connected to: ${this.host}:${this.port}`); clearTimeout(this.connectionRetry); this.connected = true; if (this.passThrough) this.createServer(); resolve(); }); - Logger.verbose("RCON", 1, `Connecting to: ${this.host}:${this.port}`); + Logger.verbose('RCON', 1, `Connecting to: ${this.host}:${this.port}`); this.connectionRetry = setTimeout(() => this.connect(), this.autoReconnectDelay); this.autoReconnect = true; this.client = net - .createConnection({ port: this.port, host: this.host }, () => this.#sendAuth()) - .on("data", (data) => this.#onData(data)) - .on("end", () => this.#onClose()) - .on("error", () => this.#onNetError()); + .createConnection({ port: this.port, host: this.host }, () => this.sendAuth()) + .on('data', (data) => this.onData(data)) + .on('end', () => this.onClose()) + .on('error', () => this.onNetError()); }).catch((error) => { - Logger.verbose("RCON", 1, `Rcon.connect() ${error}`); + Logger.verbose('RCON', 1, `Rcon.connect() ${error}`); }); } async disconnect() { return new Promise((resolve, reject) => { - Logger.verbose("RCON", 1, `Disconnecting from: ${this.host}:${this.port}`); + Logger.verbose('RCON', 1, `Disconnecting from: ${this.host}:${this.port}`); clearTimeout(this.connectionRetry); - this.removeAllListeners("server"); - this.removeAllListeners("auth"); + this.removeAllListeners('server'); + this.removeAllListeners('auth'); this.autoReconnect = false; this.client.end(); this.connected = false; this.closeServer(); resolve(); }).catch((error) => { - Logger.verbose("RCON", 1, `Rcon.disconnect() ${error}`); + Logger.verbose('RCON', 1, `Rcon.disconnect() ${error}`); }); } async execute(body) { let steamID = body.match(/\d{17}/); if (steamID) { - steamID = steamID[ 0 ] - body = body.replace(/\d{17}/, this.steamIndex[ steamID ]); + steamID = steamID[0]; + body = body.replace(/\d{17}/, this.steamIndex[steamID]); } return new Promise((resolve, reject) => { - if (!this.connected) return reject(new Error("Rcon not connected.")); - if (!this.client.writable) return reject(new Error("Unable to write to node:net socket")); + if (!this.connected) return reject(new Error('Rcon not connected.')); + if (!this.client.writable) return reject(new Error('Unable to write to node:net socket')); const string = String(body); const length = Buffer.from(string).length; - if (length > 4154) Logger.verbose("RCON", 1, `Error occurred. Oversize, "${length}" > 4154`); + if (length > 4154) Logger.verbose('RCON', 1, `Error occurred. Oversize, "${length}" > 4154`); else { const outputData = (data) => { clearTimeout(timeOut); resolve(data); }; const timedOut = () => { - console.warn("MISSED", listenerId) + console.warn('MISSED', listenerId); this.removeListener(listenerId, outputData); return reject(new Error(`Rcon response timed out`)); }; @@ -110,223 +112,288 @@ export default class Rcon extends EventEmitter { const listenerId = `response${this.msgId}`; const timeOut = setTimeout(timedOut, 10000); this.once(listenerId, outputData); - this.#send(string, this.msgId); + this.send(string, this.msgId); this.msgId++; } }).catch((error) => { - Logger.verbose("RCON", 1, `Rcon.execute() ${error}`); + Logger.verbose('RCON', 1, `Rcon.execute() ${error}`); }); } - #sendAuth() { - Logger.verbose("RCON", 1, `Sending Token to: ${this.host}:${this.port}`); - this.client.write(this.#encode(this.type.auth, 0, this.password).toString("binary"), "binary");//2147483647 + sendAuth() { + Logger.verbose('RCON', 1, `Sending Token to: ${this.host}:${this.port}`); + this.client.write(this.encode(this.type.auth, 0, this.password).toString('binary'), 'binary'); //2147483647 } - #send(body, id = 99) { - this.#write(this.type.command, id, body); - this.#write(this.type.command, id + 2); + send(body, id = 99) { + this.write(this.type.command, id, body); + this.write(this.type.command, id + 2); } - #write(type, id, body) { - Logger.verbose("RCON", 2, `Writing packet with type "${type}", id "${id}" and body "${body || ""}"`); - this.client.write(this.#encode(type, id, body).toString("binary"), "binary"); + write(type, id, body) { + Logger.verbose( + 'RCON', + 2, + `Writing packet with type "${type}", id "${id}" and body "${body || ''}"` + ); + this.client.write(this.encode(type, id, body).toString('binary'), 'binary'); } - #encode(type, id, body = "") { + encode(type, id, body = '') { const size = Buffer.byteLength(body) + 14; const buffer = new Buffer.alloc(size); buffer.writeInt32LE(size - 4, 0); buffer.writeInt32LE(id, 4); buffer.writeInt32LE(type, 8); - buffer.write(body, 12, size - 2, "utf8"); + buffer.write(body, 12, size - 2, 'utf8'); buffer.writeInt16LE(0, size - 2); return buffer; } - #onData(data) { - Logger.verbose("RCON", 4, `Got data: ${this.#bufToHexString(data)}`); - this.stream = Buffer.concat([ this.stream, data ], this.stream.byteLength + data.byteLength); + onData(data) { + Logger.verbose('RCON', 4, `Got data: ${this.bufToHexString(data)}`); + this.stream = Buffer.concat([this.stream, data], this.stream.byteLength + data.byteLength); while (this.stream.byteLength >= 7) { - const packet = this.#decode(); + const packet = this.decode(); if (!packet) break; - else Logger.verbose("RCON", 3, `Processing decoded packet: Size: ${packet.size}, ID: ${packet.id}, Type: ${packet.type}, Body: ${packet.body}`); - this.appendToFile(RCON_LOG_FILEPATH, packet.body) + else + Logger.verbose( + 'RCON', + 3, + `Processing decoded packet: Size: ${packet.size}, ID: ${packet.id}, Type: ${packet.type}, Body: ${packet.body}` + ); + this.appendToFile(RCON_LOG_FILEPATH, packet.body); if (packet.id > this.msgIdHigh) this.emit(`responseForward_1`, packet); - else if (packet.type === this.type.response) this.#onResponse(packet); - else if (packet.type === this.type.server) this.#onServer(packet); - else if (packet.type === this.type.command) this.emit("auth"); + else if (packet.type === this.type.response) this.onResponse(packet); + else if (packet.type === this.type.server) this.onServer(packet); + else if (packet.type === this.type.command) this.emit('auth'); } } - #onServer(packet) { - this.emit("server", packet); + onServer(packet) { + this.emit('server', packet); for (const client in this.rconClients) - if (this.rconClients[ client ]) { - this.emit(`serverForward_${this.rconClients[ client ].rconIdClient}`, packet.body); + if (this.rconClients[client]) { + this.emit(`serverForward_${this.rconClients[client].rconIdClient}`, packet.body); } } - #decode() { - if (this.stream[ 0 ] === 0 && this.stream[ 1 ] === 1 && this.stream[ 2 ] === 0 && this.stream[ 3 ] === 0 && this.stream[ 4 ] === 0 && this.stream[ 5 ] === 0 && this.stream[ 6 ] === 0) { + decode() { + if ( + this.stream[0] === 0 && + this.stream[1] === 1 && + this.stream[2] === 0 && + this.stream[3] === 0 && + this.stream[4] === 0 && + this.stream[5] === 0 && + this.stream[6] === 0 + ) { this.stream = this.stream.subarray(7); return this.soh; } const bufSize = this.stream.readInt32LE(0); - if (bufSize > 4154 || bufSize < 10) return this.#badPacket(); + if (bufSize > 4154 || bufSize < 10) return this.badPacket(); else if (bufSize <= this.stream.byteLength - 4 && this.stream.byteLength >= 12) { const bufId = this.stream.readInt32LE(4); const bufType = this.stream.readInt32LE(8); - if (this.stream[ bufSize + 2 ] !== 0 || this.stream[ bufSize + 3 ] !== 0 || bufId < 0 || bufType < 0 || bufType > 5) return this.#badPacket(); + if ( + this.stream[bufSize + 2] !== 0 || + this.stream[bufSize + 3] !== 0 || + bufId < 0 || + bufType < 0 || + bufType > 5 + ) + return this.badPacket(); else { - const response = { size: bufSize, id: bufId, type: bufType, body: this.stream.toString("utf8", 12, bufSize + 2) }; + const response = { + size: bufSize, + id: bufId, + type: bufType, + body: this.stream.toString('utf8', 12, bufSize + 2) + }; this.stream = this.stream.subarray(bufSize + 4); - if (response.body === "" && this.stream[ 0 ] === 0 && this.stream[ 1 ] === 1 && this.stream[ 2 ] === 0 && this.stream[ 3 ] === 0 && this.stream[ 4 ] === 0 && this.stream[ 5 ] === 0 && this.stream[ 6 ] === 0) { + if ( + response.body === '' && + this.stream[0] === 0 && + this.stream[1] === 1 && + this.stream[2] === 0 && + this.stream[3] === 0 && + this.stream[4] === 0 && + this.stream[5] === 0 && + this.stream[6] === 0 + ) { this.stream = this.stream.subarray(7); - response.body = ""; + response.body = ''; } return response; } } else return null; } - #onResponse(packet) { - if (packet.body === "") { + onResponse(packet) { + if (packet.body === '') { this.emit(`response${this.responseString.id - 2}`, this.responseString.body); - this.responseString.body = ""; - } else if (!packet.body.includes("")) { + this.responseString.body = ''; + } else if (!packet.body.includes('')) { this.responseString.body = this.responseString.body += packet.body; this.responseString.id = packet.id; - } else this.#badPacket(); + } else this.badPacket(); } - #badPacket() { - Logger.verbose("RCON", 1, `Bad packet, clearing: ${this.bufToHexString(this.stream)} Pending string: ${this.responseString}`); + badPacket() { + Logger.verbose( + 'RCON', + 1, + `Bad packet, clearing: ${this.bufToHexString(this.stream)} Pending string: ${ + this.responseString + }` + ); this.stream = Buffer.alloc(0); - this.responseString = ""; + this.responseString = ''; return null; } - #onClose() { - Logger.verbose("RCON", 1, `Socket closed`); - this.#cleanUp(); + onClose() { + Logger.verbose('RCON', 1, `Socket closed`); + this.cleanUp(); } - #onNetError(error) { - Logger.verbose("RCON", 1, `node:net error:`, error); - this.emit("RCON_ERROR", error); - this.#cleanUp(); + onNetError(error) { + Logger.verbose('RCON', 1, `node:net error:`, error); + this.emit('RCON_ERROR', error); + this.cleanUp(); } - #cleanUp() { + cleanUp() { this.closeServer(); this.connected = false; this.removeAllListeners(); clearTimeout(this.connectionRetry); if (this.autoReconnect) { - Logger.verbose("RCON", 1, `Sleeping ${this.autoReconnectDelay}ms before reconnecting`); + Logger.verbose('RCON', 1, `Sleeping ${this.autoReconnectDelay}ms before reconnecting`); this.connectionRetry = setTimeout(() => this.connect(), this.autoReconnectDelay); } } createServer() { - this.ptServer = net.createServer((client) => this.#onNewClient(client)); + this.ptServer = net.createServer((client) => this.onNewClient(client)); this.ptServer.maxConnections = this.passThroughMaxClients; - this.ptServer.on("error", (error) => this.#onSerErr(error)); - this.ptServer.on("drop", () => Logger.verbose("RCON", 1, `Pass-through Server: Max Clients Reached (${this.passThroughMaxClients}) rejecting new connection`)); - this.ptServer.listen(this.passThroughPort, () => Logger.verbose("RCON", 1, `Pass-through Server: Listening on port ${this.passThroughPort}`)); + this.ptServer.on('error', (error) => this.onSerErr(error)); + this.ptServer.on('drop', () => + Logger.verbose( + 'RCON', + 1, + `Pass-through Server: Max Clients Reached (${this.passThroughMaxClients}) rejecting new connection` + ) + ); + this.ptServer.listen(this.passThroughPort, () => + Logger.verbose('RCON', 1, `Pass-through Server: Listening on port ${this.passThroughPort}`) + ); } closeServer() { - for (const client in this.rconClients) if (this.rconClients[ client ]) this.rconClients[ client ].end(); + for (const client in this.rconClients) + if (this.rconClients[client]) this.rconClients[client].end(); if (!this.ptServer) return; - this.ptServer.close(() => this.#onServerClose()); + this.ptServer.close(() => this.onServerClose()); } - #onServerClose() { + onServerClose() { if (!this.ptServer) return; this.ptServer.removeAllListeners(); this.ptServer = null; - Logger.verbose("RCON", 1, `Pass-through Server: Closed`); + Logger.verbose('RCON', 1, `Pass-through Server: Closed`); } - #onNewClient(client) { + onNewClient(client) { client.setTimeout(this.passThroughTimeOut); - client.on("end", () => this.#onClientEnd(client)); - client.on("error", () => this.#onClientEnd(client)); - client.on("timeout", () => this.#onClientTimeOut(client)); - client.on("data", (data) => this.#onClientData(client, data)); - Logger.verbose("RCON", 1, `Pass-through Server: Client connecting`); + client.on('end', () => this.onClientEnd(client)); + client.on('error', () => this.onClientEnd(client)); + client.on('timeout', () => this.onClientTimeOut(client)); + client.on('data', (data) => this.onClientData(client, data)); + Logger.verbose('RCON', 1, `Pass-through Server: Client connecting`); } - #onSerErr(error) { + onSerErr(error) { this.closeServer(); - Logger.verbose("RCON", 1, `Pass-through Server: ${error}`); + Logger.verbose('RCON', 1, `Pass-through Server: ${error}`); } - #onClientEnd(client) { + onClientEnd(client) { if (!client.rconIdClient) return; this.removeAllListeners(`serverForward_${client.rconIdClient}`); this.removeAllListeners(`responseForward_${client.rconIdClient}`); - this.rconClients[ `${client.rconIdClient}` ] = null; - Logger.verbose("RCON", 1, `Pass-through Server: Client-${client.rconIdClient} Disconnected`); + this.rconClients[`${client.rconIdClient}`] = null; + Logger.verbose('RCON', 1, `Pass-through Server: Client-${client.rconIdClient} Disconnected`); } - #onClientTimeOut(client) { + onClientTimeOut(client) { client.end(); - Logger.verbose("RCON", 1, `Pass-through Server: Client-${client.rconIdClient} Timed Out`); + Logger.verbose('RCON', 1, `Pass-through Server: Client-${client.rconIdClient} Timed Out`); } - #onClientData(client, data) { + onClientData(client, data) { if (!client.rconStream) client.rconStream = new Buffer.alloc(0); - client.rconStream = Buffer.concat([ client.rconStream, data ], client.rconStream.byteLength + data.byteLength); + client.rconStream = Buffer.concat( + [client.rconStream, data], + client.rconStream.byteLength + data.byteLength + ); while (client.rconStream.byteLength >= 4) { - const packet = this.#decodeClient(client); + const packet = this.decodeClient(client); if (!packet) break; - if (!client.rconHasAuthed) this.#authClient(client, packet); + if (!client.rconHasAuthed) this.authClient(client, packet); else { - if (!client.rconWheel || client.rconWheel > 20) client.rconWheel = 0; else client.rconWheel++; - client.rconIdQueueNEW[ `${client.rconWheel}` ] = packet.id + client.rconIdQueueNEW[`${client.rconWheel}`] = packet.id; - const encoded = this.#encode(packet.type, this.specialId + client.rconWheel, this.#steamToEosClient(packet.body)); //////////////////////////////////////////////// - this.client.write(encoded.toString("binary"), "binary"); - // this.client.write(this.#encode(packet.type, this.specialId * client.rconIdClient).toString("binary"), "binary") + const encoded = this.encode( + packet.type, + this.specialId + client.rconWheel, + this.steamToEosClient(packet.body) + ); //////////////////////////////////////////////// + this.client.write(encoded.toString('binary'), 'binary'); + // this.client.write(this.encode(packet.type, this.specialId * client.rconIdClient).toString("binary"), "binary") } } } - #decodeClient(client) { + decodeClient(client) { const bufSize = client.rconStream.readInt32LE(0); if (bufSize <= client.rconStream.byteLength - 4) { const response = { size: bufSize, id: client.rconStream.readInt32LE(4), type: client.rconStream.readInt32LE(8), - body: client.rconStream.toString("utf8", 12, bufSize + 2), + body: client.rconStream.toString('utf8', 12, bufSize + 2) }; client.rconStream = client.rconStream.subarray(bufSize + 4); return response; } else return null; } - #authClient(client, packet) { + authClient(client, packet) { if (packet.body !== this.passThroughChallenge) { client.end(); - Logger.verbose("RCON", 1, `Pass-through Server: Client [Rejected] Password not matched`); + Logger.verbose('RCON', 1, `Pass-through Server: Client [Rejected] Password not matched`); } else { client.rconHasAuthed = true; - client.rconIdQueueNEW = {} + client.rconIdQueueNEW = {}; for (let i = 1; i <= this.passThroughMaxClients; i++) { - if (this.rconClients[ `${i}` ] === null) { + if (this.rconClients[`${i}`] === null) { client.rconIdClient = i; - this.rconClients[ `${i}` ] = client; + this.rconClients[`${i}`] = client; break; } } - this.on(`serverForward_${client.rconIdClient}`, (body) => client.write(this.#encode(1, 0, this.#eosToSteam(body)).toString("binary"), "binary")); - this.on(`responseForward_${client.rconIdClient}`, (packet) => this.#onForward(client, packet)); - client.write(this.#encode(0, packet.id)); - client.write(this.#encode(2, packet.id)); - Logger.verbose("RCON", 1, `Pass-through Server: Client-${client.rconIdClient} Connected`); + this.on(`serverForward_${client.rconIdClient}`, (body) => + client.write(this.encode(1, 0, this.eosToSteam(body)).toString('binary'), 'binary') + ); + this.on(`responseForward_${client.rconIdClient}`, (packet) => this.onForward(client, packet)); + client.write(this.encode(0, packet.id)); + client.write(this.encode(2, packet.id)); + Logger.verbose('RCON', 1, `Pass-through Server: Client-${client.rconIdClient} Connected`); } } - #onForward(client, packet) { - if (packet.body !== "" && packet.body !== "") { - - const int = packet.id - this.specialId + onForward(client, packet) { + if (packet.body !== '' && packet.body !== '') { + const int = packet.id - this.specialId; //console.log(client.rconIdQueueNEW);////////////////////////////////////////////////////////////////////////////////////////// - client.write(this.#encode(packet.type, client.rconIdQueueNEW[ int ], this.#eosToSteam(packet.body)).toString("binary"), "binary"); - } else if (packet.body != "") { - const int = packet.id - this.specialId - client.write(this.#encode(0, client.rconIdQueueNEW[ int ]).toString("binary"), "binary"); - client.write(this.#encodeSpecial(client.rconIdQueueNEW[ int ]).toString("binary"), "binary"); + client.write( + this.encode(packet.type, client.rconIdQueueNEW[int], this.eosToSteam(packet.body)).toString( + 'binary' + ), + 'binary' + ); + } else if (packet.body != '') { + const int = packet.id - this.specialId; + client.write(this.encode(0, client.rconIdQueueNEW[int]).toString('binary'), 'binary'); + client.write(this.encodeSpecial(client.rconIdQueueNEW[int]).toString('binary'), 'binary'); } } - #encodeSpecial(id) { + encodeSpecial(id) { const buffer = new Buffer.alloc(21); buffer.writeInt32LE(10, 0); buffer.writeInt32LE(id, 4); @@ -334,8 +401,8 @@ export default class Rcon extends EventEmitter { buffer.writeInt32LE(1, 15); return buffer; } - #bufToHexString(buf) { - return buf.toString("hex").match(/../g).join(" "); + bufToHexString(buf) { + return buf.toString('hex').match(/../g).join(' '); } async warn(steamID, message) { this.execute(`AdminWarn "${steamID}" ${message}`); @@ -348,34 +415,35 @@ export default class Rcon extends EventEmitter { } addIds(steamId, eosId) { - this.steamIndex[ steamId ] = eosId; // { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; - this.eosIndex[ eosId ] = steamId; + this.steamIndex[steamId] = eosId; // { "76561198799344716": "00026e21ce3d43c792613bdbb6dec1ba" }; + this.eosIndex[eosId] = steamId; } removeIds(eosId) { // clean up ids on leave } - #steamToEosClient(body) { + steamToEosClient(body) { //assume client does not send more than 1 steamId per msg const m = body.match(/[0-9]{17}/); - if (m && m[ 1 ] in this.steamIndex) return body.replaceAll(`${m[ 0 ]}`, this.steamIndex[ m[ 0 ] ]); + if (m && m[1] in this.steamIndex) return body.replaceAll(`${m[0]}`, this.steamIndex[m[0]]); return body; } - #eosToSteam(body) { + eosToSteam(body) { //split body to lines for matching (1 steamId per line) - const lines = body.split("\n"); + const lines = body.split('\n'); const nBody = []; - for (let line of lines) nBody.push(this.#matchRcon(line)); - return nBody.join("\n"); + for (let line of lines) nBody.push(this.matchRcon(line)); + return nBody.join('\n'); } - #matchRcon(line) { + matchRcon(line) { console.warn(line); - for (const r of this.defs) { + for (const r of defs) { const match = line.match(r.regex); - if (match && match.groups.eosId in this.eosIndex) return r.rep(line, this.eosIndex[ match.groups.eosId ], match.groups.eosId); + if (match && match.groups.eosId in this.eosIndex) + return r.rep(line, this.eosIndex[match.groups.eosId], match.groups.eosId); } return line; } @@ -388,7 +456,7 @@ export default class Rcon extends EventEmitter { fs.mkdirSync(dir, { recursive: true }); } - fs.appendFile(filePath, content + "\n", (err) => { + fs.appendFile(filePath, content + '\n', (err) => { if (err) throw err; }); } @@ -404,98 +472,101 @@ export default class Rcon extends EventEmitter { fs.renameSync(logFile, newFile); } } - - defs = [ - //strict matching to avoid 'name as steamId errors' - { - regex: /^ID: [0-9]+ \| SteamID: (?[0-9a-f]{32}) \| Name: .+ \| Team ID: (1|2|N\/A) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False|N\/A) \| Role: .+$/, - rep: (line, steamId, eosId) => { - return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); - }, - }, - { - regex: /^ID: (?[0-9]+) \| SteamID: (?[0-9a-f]{32}) \| Since Disconnect: (?.+) \| Name: (?.+)$/, - rep: (line, steamId, eosId) => { - return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); - }, - }, - { - regex: /^ID: (?[0-9]+) \| Name: (?.+) \| Size: (?[0-9]) \| Locked: (?True|False) \| Creator Name: (?.+) \| Creator Steam ID: (?[0-9]{17})/, - rep: (line, steamId, eosId) => { - return line.replace(` Creator Steam ID: ${eosId}`, ` Creator Steam ID: ${steamId}`); - }, - }, - { - regex: /^Forced team change for player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (.+)/, - rep: (line, steamId, eosId) => { - return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); - }, - }, - - { - regex: /^Could not find player (?[0-9a-f]{32})/, - rep: (line, steamId, eosId) => { - return line.replace(`Could not find player ${eosId}`, `Could not find player ${steamId}`); - }, - }, - - { - regex: /^\[ChatAll] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - }, - }, - { - regex: /^\[ChatTeam] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - }, - }, - { - regex: /^\[ChatSquad] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - }, - }, - { - regex: /^\[ChatAdmin] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - }, - }, - { - regex: /^(?.+) \(Steam ID: (?[0-9a-f]{32})\) has created Squad (?[0-9]+) \(Squad Name: (?.+)\) on (?.+)/, - rep: (line, steamId, eosId) => { - return line.replace(` (Steam ID: ${eosId}) `, ` (Steam ID: ${steamId}) `); - }, - }, - { - regex: /^Kicked player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (?.+)/, - rep: (line, steamId, eosId) => { - return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); - }, - }, - - { - regex: /^ERROR: Unable to find player with name or id \((?[0-9a-f]{32})\)$/, - rep: (line, steamId, eosId) => { - return line.replace(`name or id (${eosId})`, `name or id (${steamId})`); - }, - }, - { - regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has possessed admin camera./, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - }, - }, - { - regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has unpossessed admin camera./, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - }, - }, - ]; } +const defs = [ + //strict matching to avoid 'name as steamId errors' + { + regex: + /^ID: [0-9]+ \| SteamID: (?[0-9a-f]{32}) \| Name: .+ \| Team ID: (1|2|N\/A) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False|N\/A) \| Role: .+$/, + rep: (line, steamId, eosId) => { + return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); + } + }, + { + regex: + /^ID: (?[0-9]+) \| SteamID: (?[0-9a-f]{32}) \| Since Disconnect: (?.+) \| Name: (?.+)$/, + rep: (line, steamId, eosId) => { + return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); + } + }, + { + regex: + /^ID: (?[0-9]+) \| Name: (?.+) \| Size: (?[0-9]) \| Locked: (?True|False) \| Creator Name: (?.+) \| Creator Steam ID: (?[0-9]{17})/, + rep: (line, steamId, eosId) => { + return line.replace(` Creator Steam ID: ${eosId}`, ` Creator Steam ID: ${steamId}`); + } + }, + { + regex: /^Forced team change for player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (.+)/, + rep: (line, steamId, eosId) => { + return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); + } + }, + + { + regex: /^Could not find player (?[0-9a-f]{32})/, + rep: (line, steamId, eosId) => { + return line.replace(`Could not find player ${eosId}`, `Could not find player ${steamId}`); + } + }, + + { + regex: /^\[ChatAll] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + } + }, + { + regex: /^\[ChatTeam] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + } + }, + { + regex: /^\[ChatSquad] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + } + }, + { + regex: /^\[ChatAdmin] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + } + }, + { + regex: + /^(?.+) \(Steam ID: (?[0-9a-f]{32})\) has created Squad (?[0-9]+) \(Squad Name: (?.+)\) on (?.+)/, + rep: (line, steamId, eosId) => { + return line.replace(` (Steam ID: ${eosId}) `, ` (Steam ID: ${steamId}) `); + } + }, + { + regex: /^Kicked player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (?.+)/, + rep: (line, steamId, eosId) => { + return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); + } + }, + + { + regex: /^ERROR: Unable to find player with name or id \((?[0-9a-f]{32})\)$/, + rep: (line, steamId, eosId) => { + return line.replace(`name or id (${eosId})`, `name or id (${steamId})`); + } + }, + { + regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has possessed admin camera./, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + } + }, + { + regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has unpossessed admin camera./, + rep: (line, steamId, eosId) => { + return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + } + } +]; //////////////////////////////////////////////////ALL BELOW IS FOR STANDALONE TESTING/RUNNING // const Logger = { From 971c32f247d55ac83951bf510930e1cf45908be9 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 02:52:06 +0100 Subject: [PATCH 15/21] chore: removed unused dependencies, linting --- squad-server/index.js | 95 ++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/squad-server/index.js b/squad-server/index.js index 3685463..d33c791 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -1,7 +1,6 @@ import EventEmitter from 'events'; import axios from 'axios'; -import Gamedig from 'gamedig'; import Logger from 'core/logger'; import { SQUADJS_API_DOMAIN } from 'core/constants'; @@ -19,7 +18,7 @@ export default class SquadServer extends EventEmitter { constructor(options = {}) { super(); - for (const option of [ 'host', 'queryPort' ]) + for (const option of ['host']) if (!(option in options)) throw new Error(`${option} must be specified.`); this.id = options.id; @@ -107,29 +106,29 @@ export default class SquadServer extends EventEmitter { const command = data.message.match(/!([^ ]+) ?(.*)/); if (command) - this.emit(`CHAT_COMMAND:${command[ 1 ].toLowerCase()}`, { + this.emit(`CHAT_COMMAND:${command[1].toLowerCase()}`, { ...data, - message: command[ 2 ].trim() + message: command[2].trim() }); }); this.rcon.on('POSSESSED_ADMIN_CAMERA', async (data) => { data.player = await this.getPlayerBySteamID(data.steamID); - this.adminsInAdminCam[ data.steamID ] = data.time; + this.adminsInAdminCam[data.steamID] = data.time; this.emit('POSSESSED_ADMIN_CAMERA', data); }); this.rcon.on('UNPOSSESSED_ADMIN_CAMERA', async (data) => { data.player = await this.getPlayerBySteamID(data.steamID); - if (this.adminsInAdminCam[ data.steamID ]) { - data.duration = data.time.getTime() - this.adminsInAdminCam[ data.steamID ].getTime(); + if (this.adminsInAdminCam[data.steamID]) { + data.duration = data.time.getTime() - this.adminsInAdminCam[data.steamID].getTime(); } else { data.duration = 0; } - delete this.adminsInAdminCam[ data.steamID ]; + delete this.adminsInAdminCam[data.steamID]; this.emit('UNPOSSESSED_ADMIN_CAMERA', data); }); @@ -210,11 +209,15 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_CONNECTED', async (data) => { - Logger.verbose("SquadServer", 1, `Player connected ${data.playerSuffix} - SteamID: ${data.steamID} - EOSID: ${data.eosID}`) + Logger.verbose( + 'SquadServer', + 1, + `Player connected ${data.playerSuffix} - SteamID: ${data.steamID} - EOSID: ${data.eosID}` + ); - this.rcon.addIds(data.steamID, data.eosID) + this.rcon.addIds(data.steamID, data.eosID); - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.eosID); if (data.player) data.player.suffix = data.playerSuffix; delete data.steamID; @@ -224,7 +227,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_DISCONNECTED', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.playerEOSID); delete data.steamID; @@ -233,22 +236,29 @@ export default class SquadServer extends EventEmitter { this.logParser.on('PLAYER_DAMAGED', async (data) => { data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); + + if (!data.attacker.playercontroller) data.attacker.playercontroller = data.attackerController; + + if (data.victim && data.attacker) { + if (!data.victim.playercontroller) data.victim.playercontroller = data.attackerController; - if (data.victim && data.attacker) data.teamkill = data.victim.teamID === data.attacker.teamID && data.victim.steamID !== data.attacker.steamID; + } delete data.victimName; delete data.attackerName; + console.log('player damage', data); + this.emit('PLAYER_DAMAGED', data); }); this.logParser.on('PLAYER_WOUNDED', async (data) => { data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); if (!data.attacker) data.attacker = await this.getPlayerByController(data.attackerPlayerController); @@ -266,7 +276,7 @@ export default class SquadServer extends EventEmitter { this.logParser.on('PLAYER_DIED', async (data) => { data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); if (!data.attacker) data.attacker = await this.getPlayerByController(data.attackerPlayerController); @@ -282,9 +292,9 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_REVIVED', async (data) => { - data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); - data.reviver = await this.getPlayerByName(data.reviverName); + data.victim = await this.getPlayerByEOSID(data.victimEOSID); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); + data.reviver = await this.getPlayerByEOSID(data.reviverEOSID); delete data.victimName; delete data.attackerName; @@ -294,7 +304,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_POSSESS', async (data) => { - data.player = await this.getPlayerByNameSuffix(data.playerSuffix); + data.player = await this.getPlayerByEOSID(data.playerEOSID); if (data.player) data.player.possessClassname = data.possessClassname; delete data.playerSuffix; @@ -303,7 +313,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_UNPOSSESS', async (data) => { - data.player = await this.getPlayerByNameSuffix(data.playerSuffix); + data.player = await this.getPlayerByEOSID(data.playerEOSID); delete data.playerSuffix; @@ -319,8 +329,8 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('CLIENT_EXTERNAL_ACCOUNT_INFO', (data) => { - this.rcon.addIds(data.steamID, data.eosID) - }) + this.rcon.addIds(data.steamID, data.eosID); + }); // this.logParser.on('CLIENT_CONNECTED', (data) => { // Logger.verbose("SquadServer", 1, `Client connected. Connection: ${data.connection} - SteamID: ${data.steamID}`) // }) @@ -349,12 +359,12 @@ export default class SquadServer extends EventEmitter { } getAdminPermsBySteamID(steamID) { - return this.admins[ steamID ]; + return this.admins[steamID]; } getAdminsWithPermission(perm) { const ret = []; - for (const [ steamID, perms ] of Object.entries(this.admins)) { + for (const [steamID, perms] of Object.entries(this.admins)) { if (perm in perms) ret.push(steamID); } return ret; @@ -372,16 +382,16 @@ export default class SquadServer extends EventEmitter { try { const oldPlayerInfo = {}; for (const player of this.players) { - oldPlayerInfo[ player.steamID ] = player; + oldPlayerInfo[player.steamID] = player; } const players = []; for (const player of await this.rcon.getListPlayers(this)) players.push({ - ...oldPlayerInfo[ player.steamID ], + ...oldPlayerInfo[player.steamID], ...player, - playercontroller: this.logParser.eventStore.players[ player.steamID ] - ? this.logParser.eventStore.players[ player.steamID ].controller + playercontroller: this.logParser.eventStore.players[player.steamID] + ? this.logParser.eventStore.players[player.steamID].controller : null, squad: await this.getSquadByID(player.teamID, player.squadID) }); @@ -389,17 +399,17 @@ export default class SquadServer extends EventEmitter { this.players = players; for (const player of this.players) { - if (typeof oldPlayerInfo[ player.steamID ] === 'undefined') continue; - if (player.teamID !== oldPlayerInfo[ player.steamID ].teamID) + if (typeof oldPlayerInfo[player.steamID] === 'undefined') continue; + if (player.teamID !== oldPlayerInfo[player.steamID].teamID) this.emit('PLAYER_TEAM_CHANGE', { player: player, - oldTeamID: oldPlayerInfo[ player.steamID ].teamID, + oldTeamID: oldPlayerInfo[player.steamID].teamID, newTeamID: player.teamID }); - if (player.squadID !== oldPlayerInfo[ player.steamID ].squadID) + if (player.squadID !== oldPlayerInfo[player.steamID].squadID) this.emit('PLAYER_SQUAD_CHANGE', { player: player, - oldSquadID: oldPlayerInfo[ player.steamID ].squadID, + oldSquadID: oldPlayerInfo[player.steamID].squadID, newSquadID: player.squadID }); } @@ -478,9 +488,9 @@ export default class SquadServer extends EventEmitter { // }); const rawData = await this.rcon.execute(`ShowServerInfo`); - Logger.verbose("SquadServer", 3, `A2S raw data`, rawData) + Logger.verbose('SquadServer', 3, `A2S raw data`, rawData); const data = JSON.parse(rawData); - Logger.verbose("SquadServer", 2, `A2S data`, JSON.data) + Logger.verbose('SquadServer', 2, `A2S data`, JSON.data); // Logger.verbose("SquadServer", 1, `A2S data`, JSON.stringify(data, null, 2)) const info = { @@ -498,8 +508,8 @@ export default class SquadServer extends EventEmitter { currentLayer: data.MapName_s, nextLayer: data.NextLayer_s, - teamOne: data.TeamOne_s.replace(new RegExp(data.MapName_s, "i"), ''), - teamTwo: data.TeamTwo_s.replace(new RegExp(data.MapName_s, "i"), ''), + teamOne: data.TeamOne_s.replace(new RegExp(data.MapName_s, 'i'), ''), + teamTwo: data.TeamTwo_s.replace(new RegExp(data.MapName_s, 'i'), ''), matchTimeout: parseFloat(data.MatchTimeout_d), gameVersion: data.GameVersion_s @@ -537,7 +547,7 @@ export default class SquadServer extends EventEmitter { if (!forceUpdate) { matches = this.players.filter(condition); - if (matches.length === 1) return matches[ 0 ]; + if (matches.length === 1) return matches[0]; if (!retry) return null; } @@ -545,7 +555,7 @@ export default class SquadServer extends EventEmitter { await this.updatePlayerList(); matches = this.players.filter(condition); - if (matches.length === 1) return matches[ 0 ]; + if (matches.length === 1) return matches[0]; return null; } @@ -555,7 +565,7 @@ export default class SquadServer extends EventEmitter { if (!forceUpdate) { matches = this.squads.filter(condition); - if (matches.length === 1) return matches[ 0 ]; + if (matches.length === 1) return matches[0]; if (!retry) return null; } @@ -563,7 +573,7 @@ export default class SquadServer extends EventEmitter { await this.updateSquadList(); matches = this.squads.filter(condition); - if (matches.length === 1) return matches[ 0 ]; + if (matches.length === 1) return matches[0]; return null; } @@ -578,6 +588,7 @@ export default class SquadServer extends EventEmitter { async getPlayerBySteamID(steamID, forceUpdate) { return this.getPlayerByCondition((player) => player.steamID === steamID, forceUpdate); } + async getPlayerByEOSID(eosID, forceUpdate) { return this.getPlayerByCondition((player) => player.EOSID === eosID, forceUpdate); } From 2905b229a7651408114117ec612ac61c085a6a02 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 02:54:56 +0100 Subject: [PATCH 16/21] chore: defaultEnabled: true, linting --- config.json | 2 +- .../plugins/persistent-eosid-to-steamid.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config.json b/config.json index 30ef9e2..deb3b18 100644 --- a/config.json +++ b/config.json @@ -219,7 +219,7 @@ }, { "plugin": "PersistentEOSIDtoSteamID", - "enabled": false, + "enabled": true, "database": "sqlite" }, { diff --git a/squad-server/plugins/persistent-eosid-to-steamid.js b/squad-server/plugins/persistent-eosid-to-steamid.js index 0fa0598..b83458d 100644 --- a/squad-server/plugins/persistent-eosid-to-steamid.js +++ b/squad-server/plugins/persistent-eosid-to-steamid.js @@ -6,11 +6,11 @@ const { DataTypes } = Sequelize; export default class PersistentEOSIDtoSteamID extends BasePlugin { static get description() { - return "Stores into a DB every association of SteamID-EOSID"; + return 'Stores into a DB every association of SteamID-EOSID'; } static get defaultEnabled() { - return false; + return true; } static get optionsSpecification() { @@ -37,7 +37,7 @@ export default class PersistentEOSIDtoSteamID extends BasePlugin { primaryKey: true }, eosID: { - type: DataTypes.STRING, + type: DataTypes.STRING } }, { @@ -50,13 +50,13 @@ export default class PersistentEOSIDtoSteamID extends BasePlugin { } createModel(name, schema) { - this.models[ name ] = this.options.database.define(`EOS_${name}`, schema, { + this.models[name] = this.options.database.define(`EOS_${name}`, schema, { timestamps: false, indexes: [ { unique: true, - fields: [ 'eosID' ] - }, + fields: ['eosID'] + } ] }); } @@ -67,7 +67,7 @@ export default class PersistentEOSIDtoSteamID extends BasePlugin { async mount() { this.server.on('PLAYER_CONNECTED', this.onPlayerConnected); - this.verbose(1, 'Mounted') + this.verbose(1, 'Mounted'); } async unmount() { @@ -82,10 +82,10 @@ export default class PersistentEOSIDtoSteamID extends BasePlugin { } async getByEOSID(eosID) { - return await this.models.SteamIDtoEOSID.findOne({ where: { eosID: eosID } }) + return await this.models.SteamIDtoEOSID.findOne({ where: { eosID: eosID } }); } async getBySteamID(steamID) { - return await this.models.SteamIDtoEOSID.findOne({ where: { steamID: steamID } }) + return await this.models.SteamIDtoEOSID.findOne({ where: { steamID: steamID } }); } } From 978ad35b3ad8b593a92cde1aaaf92abb2041e465 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 02:55:51 +0100 Subject: [PATCH 17/21] refactor: updated log parsers for Squad V7 --- .../log-parser/adding-client-connection.js | 16 ++++----- .../check-permission-resolve-eosid.js | 24 +++++++------- .../client-external-account-info.js | 14 ++++---- squad-server/log-parser/client-login.js | 12 +++---- squad-server/log-parser/index.js | 22 +++++++------ squad-server/log-parser/join-request.js | 25 +++++++------- squad-server/log-parser/login-request.js | 28 ++++++++-------- squad-server/log-parser/player-connected.js | 33 +++++++------------ squad-server/log-parser/player-damaged.js | 7 ++-- squad-server/log-parser/player-died.js | 8 +++-- .../log-parser/player-disconnected.js | 26 ++++++++------- .../log-parser/player-join-succeeded.js | 28 ++++++++++++++++ squad-server/log-parser/player-possess.js | 6 ++-- squad-server/log-parser/player-revived.js | 9 +++-- squad-server/log-parser/player-un-possess.js | 6 ++-- squad-server/log-parser/player-wounded.js | 6 ++-- .../log-parser/playercontroller-connected.js | 10 +++--- .../log-parser/sending-auth-result.js | 12 +++---- 18 files changed, 165 insertions(+), 127 deletions(-) create mode 100644 squad-server/log-parser/player-join-succeeded.js diff --git a/squad-server/log-parser/adding-client-connection.js b/squad-server/log-parser/adding-client-connection.js index b4011e9..968cf8b 100644 --- a/squad-server/log-parser/adding-client-connection.js +++ b/squad-server/log-parser/adding-client-connection.js @@ -1,20 +1,20 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: AddClientConnection: Added client connection: \[UNetConnection\] RemoteAddr: ([\d\.]+):[0-9]+, Name: (EOSIpNetConnection_[0-9]+), Driver: GameNetDriver (EOSNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: INVALID/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: AddClientConnection: Added client connection: \[UNetConnection\] RemoteAddr: ([\d.]+):[0-9]+, Name: (EOSIpNetConnection_[0-9]+), Driver: GameNetDriver (EOSNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: INVALID/, onMatch: (args, logParser) => { const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: args[ 2 ], + raw: args[0], + time: args[1], + chainID: args[2], // steamID: args[ 3 ], - ip: args[ 3 ], - connection: args[ 4 ], - driver: args[ 5 ] + ip: args[3], + connection: args[4], + driver: args[5] }; /* This is Called when unreal engine adds a client connection First Step in Adding a Player to server */ - logParser.eventStore[ 'last-connection' ] = data; + logParser.eventStore['last-connection'] = data; logParser.emit('ADDING_CLIENT_CONNECTION', data); } }; diff --git a/squad-server/log-parser/check-permission-resolve-eosid.js b/squad-server/log-parser/check-permission-resolve-eosid.js index 853200e..6579be5 100644 --- a/squad-server/log-parser/check-permission-resolve-eosid.js +++ b/squad-server/log-parser/check-permission-resolve-eosid.js @@ -1,15 +1,15 @@ export default { - regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadCommon: SQCommonStatics Check Permissions, UniqueId:([\da-f]+)$/, - onMatch: (args, logParser) => { - const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: +args[ 2 ], - eosID: args[ 3 ], - }; + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadCommon: SQCommonStatics Check Permissions, UniqueId:([\da-f]+)$/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + eosID: args[3] + }; - logParser.eventStore.joinRequests[ data.chainID ].eosID = data.eosID; - logParser.emit('RESOLVED_EOS_ID', { ...logParser.eventStore.joinRequests[ data.chainID ] }); - } + logParser.eventStore.joinRequests[data.chainID].eosID = data.eosID; + logParser.emit('RESOLVED_EOS_ID', { ...logParser.eventStore.joinRequests[data.chainID] }); + } }; diff --git a/squad-server/log-parser/client-external-account-info.js b/squad-server/log-parser/client-external-account-info.js index 693016c..38ff55f 100644 --- a/squad-server/log-parser/client-external-account-info.js +++ b/squad-server/log-parser/client-external-account-info.js @@ -3,19 +3,19 @@ export default { /^\[([0-9.:-]+)]\[([ 0-9]+)]LogEOS: Verbose: \[LogEOSConnect] FConnectClient::CacheExternalAccountInfo - ProductUserId: (?[0-9a-f]{32}), AccountType: (\d), AccountId: (?[0-9]{17}), DisplayName: /, onMatch: (args, logParser) => { const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: args[ 2 ], + raw: args[0], + time: args[1], + chainID: args[2], eosID: args.groups.eosId, - steamID: args.groups.steamId, + steamID: args.groups.steamId }; - logParser.eventStore.players[ data.steamID ] = { + logParser.eventStore.players[data.steamID] = { eosID: data.eosID, steamID: data.steamID }; - logParser.eventStore.playersEOS[ data.eosID ] = logParser.eventStore.players[ data.steamID ] - + logParser.eventStore.playersEOS[data.eosID] = logParser.eventStore.players[data.steamID]; + logParser.emit('CLIENT_EXTERNAL_ACCOUNT_INFO', data); } }; diff --git a/squad-server/log-parser/client-login.js b/squad-server/log-parser/client-login.js index 7f2406f..e98456c 100644 --- a/squad-server/log-parser/client-login.js +++ b/squad-server/log-parser/client-login.js @@ -3,18 +3,18 @@ export default { /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Login: NewPlayer: EOSIpNetConnection \/Engine\/Transient\.(EOSIpNetConnection_[0-9]+)/, onMatch: (args, logParser) => { const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: args[ 2 ], - connection: args[ 3 ] + raw: args[0], + time: args[1], + chainID: args[2], + connection: args[3] }; /* This is Called when a player begins the Login process We use this to get a SteamID into playerConnected. 2nd Step in player connected path */ - logParser.eventStore.joinRequests[ data.chainID ].connection = data.connection; - delete logParser.eventStore.clients[ args[ 3 ] ]; + logParser.eventStore.joinRequests[data.chainID].connection = data.connection; + delete logParser.eventStore.clients[args[3]]; logParser.emit('CLIENT_LOGIN', data); } }; diff --git a/squad-server/log-parser/index.js b/squad-server/log-parser/index.js index 2223b33..fa9b373 100644 --- a/squad-server/log-parser/index.js +++ b/squad-server/log-parser/index.js @@ -19,11 +19,12 @@ import ServerTickRate from './server-tick-rate.js'; import AddingClientConnection from './adding-client-connection.js'; import ClientLogin from './client-login.js'; import PendingConnectionDestroyed from './pending-connection-destroyed.js'; -import clientExternalAccountInfo from './client-external-account-info.js'; -import sendingAuthResult from './sending-auth-result.js'; -import loginRequest from './login-request.js'; -import joinRequest from './join-request.js'; -import checkPermissionResolveEosid from './check-permission-resolve-eosid.js'; +import ClientExternalAccountInfo from './client-external-account-info.js'; +import SendingAuthResult from './sending-auth-result.js'; +import LoginRequest from './login-request.js'; +import JoinRequest from './join-request.js'; +import PlayerJoinSucceeded from './player-join-succeeded.js'; +import CheckPermissionResolveEosid from './check-permission-resolve-eosid.js'; export default class SquadLogParser extends LogParser { constructor(options) { super('SquadGame.log', options); @@ -50,11 +51,12 @@ export default class SquadLogParser extends LogParser { AddingClientConnection, ClientLogin, PendingConnectionDestroyed, - clientExternalAccountInfo, - sendingAuthResult, - loginRequest, - joinRequest, - checkPermissionResolveEosid, + ClientExternalAccountInfo, + SendingAuthResult, + LoginRequest, + JoinRequest, + PlayerJoinSucceeded, + CheckPermissionResolveEosid ]; } } diff --git a/squad-server/log-parser/join-request.js b/squad-server/log-parser/join-request.js index 3fdfd3f..5cd2e24 100644 --- a/squad-server/log-parser/join-request.js +++ b/squad-server/log-parser/join-request.js @@ -1,16 +1,15 @@ export default { - regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join request: .+\?Name=(.+)\?SplitscreenCount=\d$/, - onMatch: (args, logParser) => { - const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: +args[ 2 ], - suffix: args[ 3 ], - }; + regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join request: .+\?Name=(.+)\?SplitscreenCount=\d$/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + suffix: args[3] + }; - logParser.eventStore.joinRequests[ data.chainID ] = data; - // console.log(logParser.eventStore.loginRequests[ data.chainID ]) - logParser.emit('CLIENT_JOIN_REQUEST', data); - } + logParser.eventStore.joinRequests[data.chainID] = data; + // console.log(logParser.eventStore.loginRequests[ data.chainID ]) + logParser.emit('CLIENT_JOIN_REQUEST', data); + } }; diff --git a/squad-server/log-parser/login-request.js b/squad-server/log-parser/login-request.js index e1cc822..1ea963f 100644 --- a/squad-server/log-parser/login-request.js +++ b/squad-server/log-parser/login-request.js @@ -1,17 +1,17 @@ export default { - regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Login request: \?Name=(.+) userId: RedpointEOS:([\da-f]{32}) platform: RedpointEOS/, - onMatch: (args, logParser) => { - const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: +args[ 2 ], - suffix: args[ 3 ], - eosID: args[ 4 ] - }; + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Login request: \?Name=(.+) userId: RedpointEOS:([\da-f]{32}) platform: RedpointEOS/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + suffix: args[3], + eosID: args[4] + }; - // logParser.eventStore.loginRequests[ data.chainID ] = data; - // console.log(logParser.eventStore.loginRequests[ data.chainID ]) - logParser.emit('CLIENT_LOGIN_REQUEST', data); - } + // logParser.eventStore.loginRequests[ data.chainID ] = data; + // console.log(logParser.eventStore.loginRequests[ data.chainID ]) + logParser.emit('CLIENT_LOGIN_REQUEST', data); + } }; diff --git a/squad-server/log-parser/player-connected.js b/squad-server/log-parser/player-connected.js index 9e768c2..662e872 100644 --- a/squad-server/log-parser/player-connected.js +++ b/squad-server/log-parser/player-connected.js @@ -1,31 +1,20 @@ export default { - regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join succeeded: (.+)/, + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: PostLogin: NewPlayer: BP_PlayerController_C .+PersistentLevel\.([^\s]+) \(IP: ([\d.]+) \| Online IDs: EOS: ([0-9a-f]{32}) steam: (\d+)\)/, onMatch: (args, logParser) => { const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: +args[ 2 ], - playerSuffix: args[ 3 ] + raw: args[0], + time: args[1], + chainID: +args[2], + ip: args[4], + eosID: args[5], + steamID: args[6] }; - // console.log(`ChainID: ${data.chainID}`, logParser.eventStore.joinRequests[ data.chainID ]); - const joinRequestsData = { ...logParser.eventStore.joinRequests[ data.chainID ] }; - // console.log('loginRequestData', loginRequestData) + const joinRequestData = logParser.eventStore.joinRequests[+args[2]]; + data.connection = joinRequestData.connection; + data.playerSuffix = joinRequestData.suffix; - data.eosID = joinRequestsData.eosID - data.controller = joinRequestsData.controller - data.steamID = `${logParser.eventStore.connectionIdToSteamID.get(joinRequestsData.connection)}` - - logParser.eventStore.connectionIdToSteamID.delete(joinRequestsData.connection) - - delete logParser.eventStore.joinRequests[ +data.chainID ]; - - // Handle Reconnecting players - if (logParser.eventStore.disconnected[ data.steamID ]) { - delete logParser.eventStore.disconnected[ data.steamID ]; - } logParser.emit('PLAYER_CONNECTED', data); - // logParser.eventStore.players[ data.steamID ].suffix = data.playerSuffix - // logParser.eventStore.players[ data.steamID ].controller = data.controller } }; diff --git a/squad-server/log-parser/player-damaged.js b/squad-server/log-parser/player-damaged.js index b1057e2..7d4a777 100644 --- a/squad-server/log-parser/player-damaged.js +++ b/squad-server/log-parser/player-damaged.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Player:(.+) ActualDamage=([0-9.]+) from (.+) caused by ([A-z_0-9-]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Player:(.+) ActualDamage=([0-9.]+) from (.+) \(Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17}) \| Player Controller ID: ([^ ]+)\)caused by ([A-z_0-9-]+)_C/, onMatch: (args, logParser) => { const data = { raw: args[0], @@ -9,7 +9,10 @@ export default { victimName: args[3], damage: parseFloat(args[4]), attackerName: args[5], - weapon: args[6] + attackerEOSID: args[6], + attackerSteamID: args[7], + attackerController: args[8], + weapon: args[9] }; logParser.eventStore.session[args[3]] = data; diff --git a/squad-server/log-parser/player-died.js b/squad-server/log-parser/player-died.js index c9f1520..4a9e40f 100644 --- a/squad-server/log-parser/player-died.js +++ b/squad-server/log-parser/player-died.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Die\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) caused by ([A-z_0-9-]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Die\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17}) \| Contoller ID: ([\w\d]+)\) caused by ([A-z_0-9-]+)_C/, onMatch: (args, logParser) => { const data = { ...logParser.eventStore.session[args[3]], @@ -11,11 +11,15 @@ export default { victimName: args[3], damage: parseFloat(args[4]), attackerPlayerController: args[5], - weapon: args[6] + attackerEOSID: args[6], + attackerSteamID: args[7], + weapon: args[9] }; logParser.eventStore.session[args[3]] = data; + console.log('Die', data); + logParser.emit('PLAYER_DIED', data); } }; diff --git a/squad-server/log-parser/player-disconnected.js b/squad-server/log-parser/player-disconnected.js index 9fd86e9..8302c07 100644 --- a/squad-server/log-parser/player-disconnected.js +++ b/squad-server/log-parser/player-disconnected.js @@ -1,16 +1,18 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UChannel::Close: Sending CloseBunch\. ChIndex == [0-9]+\. Name: \[UChannel\] ChIndex: [0-9]+, Closing: [0-9]+ \[UNetConnection\] RemoteAddr: ([0-9a-f]{32}):[0-9]+, Name: SteamNetConnection_[0-9]+, Driver: GameNetDriver SteamNetDriver_[0-9]+, IsServer: YES, PC: ([^ ]+PlayerController_C_[0-9]+), Owner: [^ ]+PlayerController_C_[0-9]+/, - onMatch: (args, logParser) => { - const data = { - raw: args[0], - time: args[1], - chainID: args[2], - steamID: args[3], - playerController: args[4] - }; + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UChannel::Close: Sending CloseBunch\. ChIndex == [0-9]+\. Name: \[UChannel\] ChIndex: [0-9]+, Closing: [0-9]+ \[UNetConnection\] RemoteAddr: ([\d.]+):[\d]+, Name: EOSIpNetConnection_[0-9]+, Driver: GameNetDriver EOSNetDriver_[0-9]+, IsServer: YES, PC: ([^ ]+PlayerController_C_[0-9]+), Owner: [^ ]+PlayerController_C_[0-9]+, UniqueId: RedpointEOS:([\d\w]+)/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: args[2], + ip: args[3], + playerController: args[4], + playerEOSID: args[5] + }; - logParser.eventStore.disconnected[data.steamID] = true; - logParser.emit('PLAYER_DISCONNECTED', data); - } + logParser.eventStore.disconnected[data.steamID] = true; + + logParser.emit('PLAYER_DISCONNECTED', data); + } }; diff --git a/squad-server/log-parser/player-join-succeeded.js b/squad-server/log-parser/player-join-succeeded.js new file mode 100644 index 0000000..cae145a --- /dev/null +++ b/squad-server/log-parser/player-join-succeeded.js @@ -0,0 +1,28 @@ +export default { + regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join succeeded: (.+)/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + playerSuffix: args[3] + }; + + const joinRequestsData = { ...logParser.eventStore.joinRequests[data.chainID] }; + + data.eosID = joinRequestsData.eosID; + data.controller = joinRequestsData.controller; + data.steamID = `${logParser.eventStore.connectionIdToSteamID.get(joinRequestsData.connection)}`; + + logParser.eventStore.connectionIdToSteamID.delete(joinRequestsData.connection); + + delete logParser.eventStore.joinRequests[+data.chainID]; + + // Handle Reconnecting players + if (logParser.eventStore.disconnected[data.steamID]) { + delete logParser.eventStore.disconnected[data.steamID]; + } + + logParser.emit('JOIN_SUCCEEDED', data); + } +}; diff --git a/squad-server/log-parser/player-possess.js b/squad-server/log-parser/player-possess.js index 12ceb84..f6302eb 100644 --- a/squad-server/log-parser/player-possess.js +++ b/squad-server/log-parser/player-possess.js @@ -1,13 +1,15 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnPossess\(\): PC=(.+) Pawn=([A-z0-9_]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnPossess\(\): PC=(.+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17})\) Pawn=([A-z0-9_]+)_C/, onMatch: (args, logParser) => { const data = { raw: args[0], time: args[1], chainID: args[2], playerSuffix: args[3], - possessClassname: args[4], + playerEOSID: args[4], + playerSteamID: args[5], + possessClassname: args[6], pawn: args[5] }; diff --git a/squad-server/log-parser/player-revived.js b/squad-server/log-parser/player-revived.js index bb412ee..819a2d7 100644 --- a/squad-server/log-parser/player-revived.js +++ b/squad-server/log-parser/player-revived.js @@ -1,6 +1,7 @@ export default { // the names are currently the wrong way around in these logs - regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: (.+) has revived (.+)\./, + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: (.+) \(Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17})\) has revived (.+) \(Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17})\)\./, onMatch: (args, logParser) => { const data = { ...logParser.eventStore.session[args[3]], @@ -8,7 +9,11 @@ export default { time: args[1], chainID: args[2], reviverName: args[3], - victimName: args[4] + reviverEOSID: args[4], + reviverSteamID: args[5], + victimName: args[6], + victimEOSID: args[7], + victimSteamID: args[8] }; logParser.emit('PLAYER_REVIVED', data); diff --git a/squad-server/log-parser/player-un-possess.js b/squad-server/log-parser/player-un-possess.js index d2b9bc4..9df7e00 100644 --- a/squad-server/log-parser/player-un-possess.js +++ b/squad-server/log-parser/player-un-possess.js @@ -1,14 +1,16 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnUnPossess\(\): PC=(.+)/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnUnPossess\(\): PC=(.+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17})\)/, onMatch: (args, logParser) => { const data = { raw: args[0], time: args[1], chainID: args[2], playerSuffix: args[3], + playerEOSID: args[4], + playerSteamID: args[5], switchPossess: - args[3] in logParser.eventStore.session && logParser.eventStore.session[args[3]] === args[2] + args[4] in logParser.eventStore.session && logParser.eventStore.session[args[4]] === args[2] }; delete logParser.eventStore.session[args[3]]; diff --git a/squad-server/log-parser/player-wounded.js b/squad-server/log-parser/player-wounded.js index 9a832dc..ef61b26 100644 --- a/squad-server/log-parser/player-wounded.js +++ b/squad-server/log-parser/player-wounded.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Wound\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) caused by ([A-z_0-9-]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Wound\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17}) \| Controller ID: ([\w\d]+)\) caused by ([A-z_0-9-]+)_C/, onMatch: (args, logParser) => { const data = { ...logParser.eventStore.session[args[3]], @@ -10,7 +10,9 @@ export default { victimName: args[3], damage: parseFloat(args[4]), attackerPlayerController: args[5], - weapon: args[6] + attackerEOSID: args[6], + attackerSteamID: args[7], + weapon: args[9] }; logParser.eventStore.session[args[3]] = data; diff --git a/squad-server/log-parser/playercontroller-connected.js b/squad-server/log-parser/playercontroller-connected.js index 0e8040d..68451ab 100644 --- a/squad-server/log-parser/playercontroller-connected.js +++ b/squad-server/log-parser/playercontroller-connected.js @@ -3,13 +3,13 @@ export default { /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: PostLogin: NewPlayer: BP_PlayerController_C .+(BP_PlayerController_C_[0-9]+)/, onMatch: (args, logParser) => { const data = { - raw: args[ 0 ], - time: args[ 1 ], - chainID: +args[ 2 ], - controller: args[ 3 ] + raw: args[0], + time: args[1], + chainID: +args[2], + controller: args[3] }; - logParser.eventStore.joinRequests[ data.chainID ].controller = data.controller; + logParser.eventStore.joinRequests[data.chainID].controller = data.controller; logParser.emit('PLAYER_CONTROLLER_CONNECTED', data); } }; diff --git a/squad-server/log-parser/sending-auth-result.js b/squad-server/log-parser/sending-auth-result.js index ce0ca8c..c8f75d1 100644 --- a/squad-server/log-parser/sending-auth-result.js +++ b/squad-server/log-parser/sending-auth-result.js @@ -2,20 +2,20 @@ export default { regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogOnline: STEAM: AUTH HANDLER: Sending auth result to user (\d{17}) with flag success\? 1/, onMatch: (args, logParser) => { - if (!logParser.eventStore[ 'last-connection' ]) return; + if (!logParser.eventStore['last-connection']) return; const data = { - ...logParser.eventStore[ 'last-connection' ], - steamID: args[ 3 ] + ...logParser.eventStore['last-connection'], + steamID: args[3] }; /* This is Called when unreal engine adds a client connection First Step in Adding a Player to server */ - logParser.eventStore.clients[ data.connection ] = data.steamID; - logParser.eventStore.connectionIdToSteamID.set(data.connection, data.steamID) + logParser.eventStore.clients[data.connection] = data.steamID; + logParser.eventStore.connectionIdToSteamID.set(data.connection, data.steamID); logParser.emit('CLIENT_CONNECTED', data); - delete logParser.eventStore[ 'last-connection' ]; + delete logParser.eventStore['last-connection']; } }; From ee09fc3d63ebac2f395a7d13222f51978644febe Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 03:01:20 +0100 Subject: [PATCH 18/21] chore: conflict resolution --- squad-server/rcon.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squad-server/rcon.js b/squad-server/rcon.js index 3953038..a8d467b 100644 --- a/squad-server/rcon.js +++ b/squad-server/rcon.js @@ -171,6 +171,8 @@ export default class SquadRcon extends Rcon { let teamName; let teamID; + if (!responseSquad || responseSquad.length < 1) return squads; + for (const line of responseSquad.split('\n')) { const match = line.match( /ID: ([0-9]+) \| Name: (.+) \| Size: ([0-9]+) \| Locked: (True|False) \| Creator Name: (.+) \| Creator Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17})/ From 140bee22ea1faf0b8d32b30aa5ea336bbe49a581 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 18:46:50 +0100 Subject: [PATCH 19/21] fix: handle Teams not loaded in server info --- squad-server/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squad-server/index.js b/squad-server/index.js index d33c791..31c49b0 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -508,8 +508,8 @@ export default class SquadServer extends EventEmitter { currentLayer: data.MapName_s, nextLayer: data.NextLayer_s, - teamOne: data.TeamOne_s.replace(new RegExp(data.MapName_s, 'i'), ''), - teamTwo: data.TeamTwo_s.replace(new RegExp(data.MapName_s, 'i'), ''), + teamOne: data.TeamOne_s?.replace(new RegExp(data.MapName_s, 'i'), '') || '', + teamTwo: data.TeamTwo_s?.replace(new RegExp(data.MapName_s, 'i'), '') || '', matchTimeout: parseFloat(data.MatchTimeout_d), gameVersion: data.GameVersion_s From 4ebb14cffd2bc168cefd7ca76aafbd46ae8b1f3e Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 18:49:45 +0100 Subject: [PATCH 20/21] chore: rcon lines rewriting for legacy support --- core/rcon.js | 90 +++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/core/rcon.js b/core/rcon.js index 34cb185..1fe0bb0 100644 --- a/core/rcon.js +++ b/core/rcon.js @@ -439,11 +439,15 @@ export default class Rcon extends EventEmitter { } matchRcon(line) { - console.warn(line); for (const r of defs) { const match = line.match(r.regex); - if (match && match.groups.eosId in this.eosIndex) - return r.rep(line, this.eosIndex[match.groups.eosId], match.groups.eosId); + if (match && (match.groups.eosId in this.eosIndex || match.groups.steamId)) { + return r.rep( + line, + match.groups.steamId || this.eosIndex[match.groups.eosId], + match.groups.eosId + ); + } } return line; } @@ -477,29 +481,44 @@ const defs = [ //strict matching to avoid 'name as steamId errors' { regex: - /^ID: [0-9]+ \| SteamID: (?[0-9a-f]{32}) \| Name: .+ \| Team ID: (1|2|N\/A) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False|N\/A) \| Role: .+$/, + /^ID: [0-9]+ \| Online IDs: EOS: (?[0-9a-f]{32}) steam: (?\d{17}) \| Name: .+ \| Team ID: (1|2|N\/A) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False|N\/A) \| Role: .+$/, rep: (line, steamId, eosId) => { - return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); + return line.replace( + /\| Online IDs: EOS: [\w\d]{32} steam: \d{17} \|/, + `| SteamID: ${steamId} |` + ); } }, { regex: - /^ID: (?[0-9]+) \| SteamID: (?[0-9a-f]{32}) \| Since Disconnect: (?.+) \| Name: (?.+)$/, + /^ID: (?[0-9]+) \| Online IDs: EOS: (?[0-9a-f]{32}) steam: (?\d{17}) \| Since Disconnect: (?.+) \| Name: (?.+)$/, rep: (line, steamId, eosId) => { - return line.replace(` SteamID: ${eosId} `, ` SteamID: ${steamId} `); + return line.replace( + /Online IDs: EOS: (?[0-9a-f]{32}) steam: (?\d{17})/, + `SteamID: ${steamId}` + ); } }, { regex: - /^ID: (?[0-9]+) \| Name: (?.+) \| Size: (?[0-9]) \| Locked: (?True|False) \| Creator Name: (?.+) \| Creator Steam ID: (?[0-9]{17})/, + /^ID: (?[0-9]+) \| Name: (?.+) \| Size: (?[0-9]) \| Locked: (?True|False) \| Creator Name: (?.+) \| Creator Online IDs: EOS: (?[\d\w]{32}) steam: (?\d{17})/, rep: (line, steamId, eosId) => { - return line.replace(` Creator Steam ID: ${eosId}`, ` Creator Steam ID: ${steamId}`); + console.log(line, steamId, eosId); + const ret = line.replace( + /\| Creator Online IDs: EOS: [\w\d]{32} steam: \d{17}/, + `| Creator Steam ID: ${steamId}` + ); + return ret; } }, { - regex: /^Forced team change for player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (.+)/, + regex: + /^Forced team change for player (?[0-9]+). \[Online IDs= EOS: (?[0-9a-f]{32}) steam: (?\d{17})] (.+)/, rep: (line, steamId, eosId) => { - return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); + return line.replace( + /Online IDs= EOS: (?[0-9a-f]{32}) steam: (?\d{17})/, + `steamid=${steamId}` + ); } }, @@ -511,40 +530,30 @@ const defs = [ }, { - regex: /^\[ChatAll] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, + regex: + /^\[Chat(All|Team|Squad|Admin)] \[Online IDs:EOS: (?[\d\w]{32}) steam: (?\d{17})] (?.+) : (?.+)/, rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - } - }, - { - regex: /^\[ChatTeam] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - } - }, - { - regex: /^\[ChatSquad] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - } - }, - { - regex: /^\[ChatAdmin] \[SteamID:(?[0-9a-f]{32})] ((?.+) : (?.+))/, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + return line.replace(/Online IDs:EOS: [\d\w]{32} steam: \d{17}/, `SteamID:${steamId}`); } }, { regex: - /^(?.+) \(Steam ID: (?[0-9a-f]{32})\) has created Squad (?[0-9]+) \(Squad Name: (?.+)\) on (?.+)/, + /^(?.+) \(Online IDs: EOS: (?[0-9a-f]{32}) steam: (?\d+)\) has created Squad (?[0-9]+) \(Squad Name: (?.+)\) on (?.+)/, rep: (line, steamId, eosId) => { - return line.replace(` (Steam ID: ${eosId}) `, ` (Steam ID: ${steamId}) `); + return line.replace( + /Online IDs: EOS: (?[0-9a-f]{32}) steam: (?\d+)/, + `Steam ID: ${steamId}` + ); } }, { - regex: /^Kicked player (?[0-9]+). \[steamid=(?[0-9a-f]{32})] (?.+)/, + regex: + /^Kicked player (?[0-9]+). \[Online IDs= EOS: (?[0-9a-f]{32}) steam: (?\d{17})] (?.+)/, rep: (line, steamId, eosId) => { - return line.replace(` [steamid=${eosId}] `, ` [steamid=${steamId}] `); + return line.replace( + /Online IDs= EOS: (?[0-9a-f]{32}) steam: (?\d{17})/, + `steamid=${steamId}` + ); } }, @@ -555,15 +564,10 @@ const defs = [ } }, { - regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has possessed admin camera./, + regex: + /^\[Online I(d|D)s:EOS: (?[0-9a-f]{32}) steam: (?)\d{17}] (?.+) has (un)?possessed admin camera\./, rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); - } - }, - { - regex: /^\[SteamID:(?[0-9a-f]{32})] (?.+) has unpossessed admin camera./, - rep: (line, steamId, eosId) => { - return line.replace(` [SteamID:${eosId}] `, ` [SteamID:${steamId}] `); + return line.replace(/Online I(d|D)s:EOS: [\w\d]{32} steam: \d{17}/, `SteamID:${steamId}`); } } ]; From 4637bc78d6b3955e6da8a4b958791441f472e547 Mon Sep 17 00:00:00 2001 From: Fantino Davide Date: Tue, 12 Dec 2023 19:04:22 +0100 Subject: [PATCH 21/21] chore: added config options for rconPassThrough in config-template --- config.json | 3 +++ squad-server/templates/config-template.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/config.json b/config.json index d0dbd46..47f77f4 100644 --- a/config.json +++ b/config.json @@ -5,6 +5,9 @@ "queryPort": 27165, "rconPort": 21114, "rconPassword": "password", + "rconPassThrough": true, + "rconPassThroughPort": 8124, + "dumpRconResponsesToFile": false, "logReaderMode": "tail", "logDir": "C:/path/to/squad/log/folder", "ftp": { diff --git a/squad-server/templates/config-template.json b/squad-server/templates/config-template.json index f8f6458..be87345 100644 --- a/squad-server/templates/config-template.json +++ b/squad-server/templates/config-template.json @@ -5,6 +5,9 @@ "queryPort": 27165, "rconPort": 21114, "rconPassword": "password", + "rconPassThrough": true, + "rconPassThroughPort": 8124, + "dumpRconResponsesToFile": false, "logReaderMode": "tail", "logDir": "C:/path/to/squad/log/folder", "ftp": {