Skip to content

Commit

Permalink
Add support for workshop maps in CS2
Browse files Browse the repository at this point in the history
  • Loading branch information
Taraman17 committed Feb 23, 2024
1 parent f58cf5e commit 4653238
Show file tree
Hide file tree
Showing 9 changed files with 771 additions and 643 deletions.
14 changes: 12 additions & 2 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@

/* Optional settings */
// Anything you want your server command line to have additional to:
// -console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging +logaddress_add_http "http://${this._localIp}:${this.logPort}/log"
// -console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging
// +logaddress_add_http "http://${this._localIp}:${this.logPort}/log"
"csgoOptionalArgs": "",
// steam serverToken for public access. To get one see https://steamcommunity.com/dev/managegameservers
"serverToken": "",
// Steam Web API token. Needed to get mapdetails like thumbnail, etc
// See https://steamcommunity.com/dev/apikey how to get one.
"apiToken": "",
// Workshop Collection to host on the server.
"workshopCollection": "",
// List of workshop ids of maps to add to available maps.
"workshopMaps": [ // '23423523523525',
// '37281723987123'
],
// If you want to use a different name / location for the update script (absolute path).
"updateScript": "",
// Time in minutes, after which a new login is needed.
Expand All @@ -42,7 +52,7 @@
// If you use https, add the path to the certificate files here.
"httpsCertificate": "",
"httpsPrivateKey": "",
// Optional: In case your CA is not trusted by default (e.g. letsencrypt), you can add
// In case your CA is not trusted by default (e.g. letsencrypt), you can add
// the CA-Cert here.
"httpsCa": "",
// Change this to any string of your liking to make it harder for attackers to profile your cookies.
Expand Down
21 changes: 15 additions & 6 deletions modules/apiV10.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* @requires node-pty
* @requires express
* @requires ./config.js
* @requires ./emitters.js
* @requires ./serverInfo.js
* @requires ./controlEmitter.js
* @requires ./sharedFunctions.js
*/

Expand Down Expand Up @@ -601,7 +602,7 @@ router.get('/control/update', (req, res) => {
* @apiName changemap
* @apiGroup Control
*
* @apiParam {string} mapname filename of the map without extension (.bsp)
* @apiParam {string/int} map name, title or workshopID of a map.
* @apiParamExample {string} Map-example
* cs_italy
*
Expand All @@ -619,16 +620,24 @@ router.get('/control/changemap', (req, res) => {
if (serverInfo.serverState.operationPending == 'none') {
controlEmitter.emit('exec', 'mapchange', 'start');
// only try to change map, if it exists on the server.
if (serverInfo.mapsAvail.includes(args.map)) {
sf.executeRcon(`map ${args.map}`).then((answer) => {
// Answer on sucess:
let map = sf.getMap(args.map);
if (map != undefined) {
let mapchangeCommand = '';
if (map.official) {
mapchangeCommand = `map ${map.name}`;
} else {
mapchangeCommand = `host_workshop_map ${map.workshopID}`
}

sf.executeRcon(mapchangeCommand).then((answer) => {
// Answer on success (unfortunately only available for official maps):
// Changelevel to de_nuke
// changelevel "de_nuke"
// CHostStateMgr::QueueNewRequest( Changelevel (de_nuke), 5 )
//
// Answer on failure:
// changelevel de_italy: invalid map name
if (answer.indexOf(`CHostStateMgr::QueueNewRequest( Changelevel (${args.map})`) == -1) {
if (map.official && answer.indexOf(`CHostStateMgr::QueueNewRequest( Changelevel (${map.name})`) == -1) {
// If the mapchange command fails, return failure immediately
res.status(501).json({ "error": `Mapchange failed: ${answer}` });
controlEmitter.emit('exec', 'mapchange', 'fail');
Expand Down
16 changes: 16 additions & 0 deletions modules/configClass.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class config {
return `-console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging +logaddress_add_http "http://${this._localIp}:${this.logPort}/log" ${this._userOptions.csgoOptionalArgs}`;
}

get apiToken() {
return this._userOptions.apiToken;
}
get rconPass() {
return this._userOptions.rconPass;
}
Expand All @@ -22,6 +25,19 @@ class config {
return this._userOptions.admins;
}

get workshopCollection() {
return this._userOptions.workshopCollection;
}
set workshopCollection(id) {
this._userOptions.workshopCollection = id;
}
get workshopMaps() {
return this._userOptions.workshopMaps;
}
set workshopMaps(maps) {
this._userOptions.workshopMaps = maps;
}

get redirectPage() {
if (this._userOptions.redirectPage) {
return this._userOptions.redirectPage;
Expand Down
240 changes: 122 additions & 118 deletions modules/logreceive.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,119 +1,123 @@
const express = require('express');
var router = express.Router();
const { exec } = require('child_process');
var controlEmitter = require('./controlEmitter.js');
const logger = require('./logger.js');
var serverInfo = require('./serverInfo.js');
const sf = require('./sharedFunctions.js');
var cfg = require('./configClass.js');

router.post('/log', (req, res) => {
const data = req.body;
var logs = data.split(/\r\n|\r|\n/);

logs.forEach(line => {
if (line.length >= 20) {
// Start authentication, when not authenticated.
if ((line.indexOf('Log file started') != -1) && !serverInfo.serverState.authenticated) {
// Start of logfile
// L 08/13/2020 - 21:48:49: Log file started (file "logs/L000_000_000_000_27015_202008132148_000.log") (game "/home/user/csgo_ds/csgo") (version "7929")
logger.verbose('start authenticating RCON');
// Since authentication is a vital step for the API to work, we start it automatically
// once the server runs.
sf.authenticate().then((data) => {
logger.verbose(`authentication ${data.authenticated}`);
}).catch((data) => {
logger.verbose(`authentication ${data.authenticated}`);
});
if (cfg.script('logStart') != '') {
exec(cfg.script('logStart'));
}
} else if (line.indexOf('Loading map ') != -1) {
// Start of map.
// L 10/13/2023 - 14:28:38: Loading map "de_anubis"
let rex = /Loading map \"(\S+)\"/g;
let matches = rex.exec(line);
let mapstring = matches[1];
mapstring = sf.cutMapName(mapstring);
serverInfo.map = mapstring;
serverInfo.pause = false;
// since 'started map' is also reported on server-start, only emit on mapchange.
if (serverInfo.serverState.operationPending == 'mapchange') {
controlEmitter.emit('exec', 'mapchange', 'end');
}
logger.verbose(`Started map: ${mapstring}`);
serverInfo.clearPlayers();
serverInfo.newMatch();
if (cfg.script('mapStart') != '') {
exec(cfg.script('mapStart'));
}
} else if (line.indexOf('World triggered "Match_Start" on') != -1) {
// Start of a new match.
// L 08/13/2020 - 21:49:26: World triggered "Match_Start" on "de_nuke"
logger.verbose('Detected match start.');
sf.queryMaxRounds();
serverInfo.newMatch();
let rex = /World triggered "Match_Start" on "(.+)"/
let matches = rex.exec(line)
serverInfo.map = matches[1];
if (cfg.script('matchStart') != '') {
exec(cfg.script('matchStart'));
}
} else if (line.indexOf('World triggered "Round_Start"') != -1) {
// Start of round.
// L 08/13/2020 - 21:49:28: World triggered "Round_Start"
if (cfg.script('roundStart') != '') {
exec(cfg.script('roundStart'));
}
} else if (/Team \"\S+\" scored/.test(line)) {
// Team scores at end of round.
// L 02/10/2019 - 21:31:15: Team "CT" scored "1" with "2" players
// L 02/10/2019 - 21:31:15: Team "TERRORIST" scored "1" with "2" players
rex = /Team \"(\S)\S+\" scored \"(\d+)\"/g;
let matches = rex.exec(line);
serverInfo.score = matches;
} else if (line.indexOf('World triggered "Round_End"') != -1) {
// End of round.
// L 08/13/2020 - 22:24:22: World triggered "Round_End"
if (cfg.script('roundEnd') != '') {
exec(cfg.script('roundEnd'));
}
} else if (line.indexOf("Game Over:") != -1) {
// End of match.
// L 08/13/2020 - 22:24:22: Game Over: competitive 131399785 de_nuke score 16:9 after 35 min
if (cfg.script('matchEnd') != '') {
exec(cfg.script('matchEnd'));
}
} else if (/\".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]>/.test(line)) {
// Player join or teamchange.
// 10/12/2023 - 16:06:38: "[Klosser] Taraman<2><[U:1:12610374]><>" entered the game
// 10/12/2023 - 18:57:47: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <Unassigned> to <CT>
// 10/12/2023 - 18:59:25: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <TERRORIST> to <Spectator>
// 10/16/2023 - 16:31:59.699 - "[Klosser] Taraman<2><[U:1:12610374]><CT>" disconnected (reason "NETWORK_DISCONNECT_DISCONNECT_BY_USER")
let rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]></g;
let matches = rex.exec(line);
if (line.indexOf('entered the game') != -1) {
serverInfo.addPlayer({ 'name': matches[1], 'steamID': matches[2] });
} else if (line.search(/disconnected \(reason/) != -1) {
logger.debug(line);
serverInfo.removePlayer(matches[2]);
} else if (line.indexOf('switched from team') != -1) {
rex = /<\[(\w:\d:\d{1,10})\]>\" switched from team <\S{1,10}> to <(\S{1,10})>/g;
matches = rex.exec(line);
serverInfo.assignPlayer(matches[1], matches[2]);
}
} else if (line.indexOf('Log file closed') != -1) {
// end of current log file. (Usually on mapchange or server quit.)
// L 08/13/2020 - 22:25:00: Log file closed
logger.verbose('logfile closed!');
if (cfg.script('logEnd') != '') {
exec(cfg.script('logEnd'));
}
}
}
});

res.status(200).send("Receiving logs");
});

const express = require('express');
var router = express.Router();
const { exec } = require('child_process');
var controlEmitter = require('./controlEmitter.js');
const logger = require('./logger.js');
var serverInfo = require('./serverInfo.js');
const sf = require('./sharedFunctions.js');
var cfg = require('./configClass.js');

router.post('/log', (req, res) => {
const data = req.body;
var logs = data.split(/\r\n|\r|\n/);

logs.forEach(line => {
if (line.length >= 20) {
// Start authentication, when not authenticated.
if ((line.indexOf('Log file started') != -1) && !serverInfo.serverState.authenticated) {
// Start of logfile
// L 08/13/2020 - 21:48:49: Log file started (file "logs/L000_000_000_000_27015_202008132148_000.log") (game "/home/user/csgo_ds/csgo") (version "7929")
logger.verbose('start authenticating RCON');
// Since authentication is a vital step for the API to work, we start it automatically
// once the server runs.
sf.authenticate().then((data) => {
logger.verbose(`authentication ${data.authenticated}`);
}).catch((data) => {
logger.verbose(`authentication ${data.authenticated}`);
});
if (cfg.script('logStart') != '') {
exec(cfg.script('logStart'));
}
} else if (line.indexOf('Loading map ') != -1) {
// Start of map.
// L 10/13/2023 - 14:28:38: Loading map "de_anubis"
let rex = /Loading map \"(\S+)\"/g;
let matches = rex.exec(line);
let mapstring = matches[1];
mapstring = sf.cutMapName(mapstring);
serverInfo.map = mapstring;
serverInfo.pause = false;
// since 'started map' is also reported on server-start, only emit on mapchange.
if (serverInfo.serverState.operationPending == 'mapchange') {
controlEmitter.emit('exec', 'mapchange', 'end');
}
logger.verbose(`Started map: ${mapstring}`);
serverInfo.clearPlayers();
serverInfo.newMatch();
if (cfg.script('mapStart') != '') {
exec(cfg.script('mapStart'));
}
} else if (line.indexOf('World triggered "Match_Start" on') != -1) {
// Start of a new match.
// L 08/13/2020 - 21:49:26: World triggered "Match_Start" on "de_nuke"
logger.verbose('Detected match start.');
sf.queryMaxRounds();
serverInfo.newMatch();
let rex = /World triggered "Match_Start" on "(.+)"/
let matches = rex.exec(line)
serverInfo.map = matches[1];
if (cfg.script('matchStart') != '') {
exec(cfg.script('matchStart'));
}
} else if (line.indexOf('World triggered "Round_Start"') != -1) {
// Start of round.
// L 08/13/2020 - 21:49:28: World triggered "Round_Start"
if (cfg.script('roundStart') != '') {
exec(cfg.script('roundStart'));
}
} else if (/Team \"\S+\" scored/.test(line)) {
// Team scores at end of round.
// L 02/10/2019 - 21:31:15: Team "CT" scored "1" with "2" players
// L 02/10/2019 - 21:31:15: Team "TERRORIST" scored "1" with "2" players
rex = /Team \"(\S)\S+\" scored \"(\d+)\"/g;
let matches = rex.exec(line);
serverInfo.score = matches;
} else if (line.indexOf('World triggered "Round_End"') != -1) {
// End of round.
// L 08/13/2020 - 22:24:22: World triggered "Round_End"
if (cfg.script('roundEnd') != '') {
exec(cfg.script('roundEnd'));
}
} else if (line.indexOf("Game Over:") != -1) {
// End of match.
// L 08/13/2020 - 22:24:22: Game Over: competitive 131399785 de_nuke score 16:9 after 35 min
if (cfg.script('matchEnd') != '') {
exec(cfg.script('matchEnd'));
}
} else if (/\".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]>/.test(line)) {
// Player join or teamchange.
// 10/12/2023 - 16:06:38: "[Klosser] Taraman<2><[U:1:12610374]><>" entered the game
// 10/12/2023 - 18:57:47: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <Unassigned> to <CT>
// 10/12/2023 - 18:59:25: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <TERRORIST> to <Spectator>
// 10/16/2023 - 16:31:59.699 - "[Klosser] Taraman<2><[U:1:12610374]><CT>" disconnected (reason "NETWORK_DISCONNECT_DISCONNECT_BY_USER")
// "Strapper<6><BOT><TERRORIST>"
let rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]></g;
let matches = rex.exec(line);
if (line.indexOf('entered the game') != -1) {
serverInfo.addPlayer({ 'name': matches[1], 'steamID': matches[2] });
} else if (line.search(/disconnected \(reason/) != -1) {
serverInfo.removePlayer(matches[2]);
} else if (line.indexOf('switched from team') != -1) {
rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]>\" switched from team <\S{1,10}> to <(\S{1,10})>/g;
matches = rex.exec(line);
serverInfo.assignPlayer(matches[1], matches[2], matches[3]);
} else if (line.search(/\[\w:\d:\d{1,10}\]><\w{1,10}>\" \[.{1,5} .{1,5} .{1,5}\] killed \".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]/) != -1) {
rex = /\[(\w:\d:\d{1,10})\]><\w{1,10}>\" \[.{1,5} .{1,5} .{1,5}\] killed \".{1,32}<\d{1,3}><\[(\w:\d:\d{1,10})\]/
matches = rex.exec(line);
serverInfo.recordKill(matches[1], matches[2]);
}
} else if (line.indexOf('Log file closed') != -1) {
// end of current log file. (Usually on mapchange or server quit.)
// L 08/13/2020 - 22:25:00: Log file closed
logger.verbose('logfile closed!');
if (cfg.script('logEnd') != '') {
exec(cfg.script('logEnd'));
}
}
}
});

res.status(200).send("Receiving logs");
});

module.exports = router;
Loading

0 comments on commit 4653238

Please sign in to comment.