gamedigz 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/GAMES_LIST.md +453 -0
  2. package/LICENSE +21 -0
  3. package/README.md +144 -0
  4. package/bin/gamedig.js +79 -0
  5. package/lib/DnsResolver.js +76 -0
  6. package/lib/GlobalUdpSocket.js +69 -0
  7. package/lib/HexUtil.js +20 -0
  8. package/lib/Logger.js +45 -0
  9. package/lib/Promises.js +18 -0
  10. package/lib/ProtocolResolver.js +7 -0
  11. package/lib/QueryRunner.js +95 -0
  12. package/lib/Results.js +32 -0
  13. package/lib/game-resolver.js +17 -0
  14. package/lib/gamedig.js +23 -0
  15. package/lib/games.js +2747 -0
  16. package/lib/index.js +5 -0
  17. package/lib/reader.js +172 -0
  18. package/package.json +74 -0
  19. package/protocols/armagetron.js +65 -0
  20. package/protocols/asa.js +12 -0
  21. package/protocols/ase.js +45 -0
  22. package/protocols/assettocorsa.js +40 -0
  23. package/protocols/battlefield.js +162 -0
  24. package/protocols/beammp.js +32 -0
  25. package/protocols/beammpmaster.js +17 -0
  26. package/protocols/buildandshoot.js +55 -0
  27. package/protocols/core.js +349 -0
  28. package/protocols/cs2d.js +65 -0
  29. package/protocols/dayz.js +196 -0
  30. package/protocols/discord.js +29 -0
  31. package/protocols/doom3.js +148 -0
  32. package/protocols/eco.js +20 -0
  33. package/protocols/eldewrito.js +21 -0
  34. package/protocols/epic.js +95 -0
  35. package/protocols/ffow.js +38 -0
  36. package/protocols/fivem.js +33 -0
  37. package/protocols/gamespy1.js +181 -0
  38. package/protocols/gamespy2.js +144 -0
  39. package/protocols/gamespy3.js +197 -0
  40. package/protocols/geneshift.js +46 -0
  41. package/protocols/goldsrc.js +8 -0
  42. package/protocols/hexen2.js +14 -0
  43. package/protocols/index.js +61 -0
  44. package/protocols/jc2mp.js +16 -0
  45. package/protocols/kspdmp.js +28 -0
  46. package/protocols/mafia2mp.js +41 -0
  47. package/protocols/mafia2online.js +9 -0
  48. package/protocols/minecraft.js +102 -0
  49. package/protocols/minecraftbedrock.js +72 -0
  50. package/protocols/minecraftvanilla.js +87 -0
  51. package/protocols/mumble.js +39 -0
  52. package/protocols/mumbleping.js +24 -0
  53. package/protocols/nadeo.js +86 -0
  54. package/protocols/openttd.js +127 -0
  55. package/protocols/quake1.js +9 -0
  56. package/protocols/quake2.js +88 -0
  57. package/protocols/quake3.js +24 -0
  58. package/protocols/rfactor.js +69 -0
  59. package/protocols/samp.js +102 -0
  60. package/protocols/savage2.js +25 -0
  61. package/protocols/starmade.js +67 -0
  62. package/protocols/starsiege.js +10 -0
  63. package/protocols/teamspeak2.js +71 -0
  64. package/protocols/teamspeak3.js +69 -0
  65. package/protocols/terraria.js +24 -0
  66. package/protocols/tribes1.js +153 -0
  67. package/protocols/tribes1master.js +80 -0
  68. package/protocols/unreal2.js +150 -0
  69. package/protocols/ut3.js +45 -0
  70. package/protocols/valve.js +455 -0
  71. package/protocols/vcmp.js +10 -0
  72. package/protocols/ventrilo.js +237 -0
  73. package/protocols/warsow.js +13 -0
@@ -0,0 +1,71 @@
1
+ import Core from './core.js'
2
+
3
+ export default class teamspeak2 extends Core {
4
+ async run (state) {
5
+ const queryPort = this.options.teamspeakQueryPort || 51234
6
+
7
+ await this.withTcp(async socket => {
8
+ {
9
+ const data = await this.sendCommand(socket, 'sel ' + this.options.port)
10
+ if (data !== '[TS]') throw new Error('Invalid header')
11
+ }
12
+
13
+ {
14
+ const data = await this.sendCommand(socket, 'si')
15
+ for (const line of data.split('\r\n')) {
16
+ const equals = line.indexOf('=')
17
+ const key = equals === -1 ? line : line.substring(0, equals)
18
+ const value = equals === -1 ? '' : line.substring(equals + 1)
19
+ state.raw[key] = value
20
+ }
21
+ }
22
+
23
+ {
24
+ const data = await this.sendCommand(socket, 'pl')
25
+ const split = data.split('\r\n')
26
+ const fields = split.shift().split('\t')
27
+ for (const line of split) {
28
+ const split2 = line.split('\t')
29
+ const player = {}
30
+ split2.forEach((value, i) => {
31
+ let key = fields[i]
32
+ if (!key) return
33
+ if (key === 'nick') key = 'name'
34
+ const m = value.match(/^"(.*)"$/)
35
+ if (m) value = m[1]
36
+ player[key] = value
37
+ })
38
+ state.players.push(player)
39
+ }
40
+ state.numplayers = state.players.length
41
+ }
42
+
43
+ {
44
+ const data = await this.sendCommand(socket, 'cl')
45
+ const split = data.split('\r\n')
46
+ const fields = split.shift().split('\t')
47
+ state.raw.channels = []
48
+ for (const line of split) {
49
+ const split2 = line.split('\t')
50
+ const channel = {}
51
+ split2.forEach((value, i) => {
52
+ const key = fields[i]
53
+ if (!key) return
54
+ const m = value.match(/^"(.*)"$/)
55
+ if (m) value = m[1]
56
+ channel[key] = value
57
+ })
58
+ state.raw.channels.push(channel)
59
+ }
60
+ }
61
+ }, queryPort)
62
+ }
63
+
64
+ async sendCommand (socket, cmd) {
65
+ return await this.tcpSend(socket, cmd + '\x0A', buffer => {
66
+ if (buffer.length < 6) return
67
+ if (buffer.slice(-6).toString() !== '\r\nOK\r\n') return
68
+ return buffer.slice(0, -6).toString()
69
+ })
70
+ }
71
+ }
@@ -0,0 +1,69 @@
1
+ import Core from './core.js'
2
+
3
+ export default class teamspeak3 extends Core {
4
+ async run (state) {
5
+ const queryPort = this.options.teamspeakQueryPort || 10011
6
+
7
+ await this.withTcp(async socket => {
8
+ {
9
+ const data = await this.sendCommand(socket, 'use port=' + this.options.port, true)
10
+ const split = data.split('\n\r')
11
+ if (split[0] !== 'TS3') throw new Error('Invalid header')
12
+ }
13
+
14
+ {
15
+ const data = await this.sendCommand(socket, 'serverinfo')
16
+ state.raw = data[0]
17
+ if ('virtualserver_name' in state.raw) state.name = state.raw.virtualserver_name
18
+ if ('virtualserver_maxclients' in state.raw) state.maxplayers = state.raw.virtualserver_maxclients
19
+ if ('virtualserver_clientsonline' in state.raw) state.numplayers = state.raw.virtualserver_clientsonline
20
+ }
21
+
22
+ {
23
+ const list = await this.sendCommand(socket, 'clientlist')
24
+ for (const client of list) {
25
+ client.name = client.client_nickname
26
+ delete client.client_nickname
27
+ if (client.client_type === '0') {
28
+ state.players.push(client)
29
+ }
30
+ }
31
+ }
32
+
33
+ {
34
+ const data = await this.sendCommand(socket, 'channellist -topic')
35
+ state.raw.channels = data
36
+ }
37
+ }, queryPort)
38
+ }
39
+
40
+ async sendCommand (socket, cmd, raw) {
41
+ const body = await this.tcpSend(socket, cmd + '\x0A', (buffer) => {
42
+ if (buffer.length < 21) return
43
+ if (buffer.slice(-21).toString() !== '\n\rerror id=0 msg=ok\n\r') return
44
+ return buffer.slice(0, -21).toString()
45
+ })
46
+
47
+ if (raw) {
48
+ return body
49
+ } else {
50
+ const segments = body.split('|')
51
+ const out = []
52
+ for (const line of segments) {
53
+ const split = line.split(' ')
54
+ const unit = {}
55
+ for (const field of split) {
56
+ const equals = field.indexOf('=')
57
+ const key = equals === -1 ? field : field.substring(0, equals)
58
+ const value = equals === -1
59
+ ? ''
60
+ : field.substring(equals + 1)
61
+ .replace(/\\s/g, ' ').replace(/\\\//g, '/')
62
+ unit[key] = value
63
+ }
64
+ out.push(unit)
65
+ }
66
+ return out
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,24 @@
1
+ import Core from './core.js'
2
+
3
+ export default class terraria extends Core {
4
+ async run (state) {
5
+ const json = await this.request({
6
+ url: 'http://' + this.options.address + ':' + this.options.port + '/v2/server/status',
7
+ searchParams: {
8
+ players: 'true',
9
+ token: this.options.token
10
+ },
11
+ responseType: 'json'
12
+ })
13
+
14
+ if (json.status !== '200') throw new Error('Invalid status')
15
+
16
+ for (const one of json.players) {
17
+ state.players.push({ name: one.nickname, team: one.team })
18
+ }
19
+
20
+ state.name = json.name
21
+ state.gamePort = json.port
22
+ state.numplayers = json.playercount
23
+ }
24
+ }
@@ -0,0 +1,153 @@
1
+ import Core from './core.js'
2
+
3
+ export default class tribes1 extends Core {
4
+ constructor () {
5
+ super()
6
+ this.encoding = 'latin1'
7
+ this.requestByte = 0x62
8
+ this.responseByte = 0x63
9
+ this.challenge = 0x01
10
+ }
11
+
12
+ async run (state) {
13
+ const query = Buffer.alloc(3)
14
+ query.writeUInt8(this.requestByte, 0)
15
+ query.writeUInt16LE(this.challenge, 1)
16
+ const reader = await this.udpSend(query, (buffer) => {
17
+ const reader = this.reader(buffer)
18
+ const responseByte = reader.uint(1)
19
+ if (responseByte !== this.responseByte) {
20
+ this.logger.debug('Unexpected response byte')
21
+ return
22
+ }
23
+ const challenge = reader.uint(2)
24
+ if (challenge !== this.challenge) {
25
+ this.logger.debug('Unexpected challenge')
26
+ return
27
+ }
28
+ const requestByte = reader.uint(1)
29
+ if (requestByte !== this.requestByte) {
30
+ this.logger.debug('Unexpected request byte')
31
+ return
32
+ }
33
+ return reader
34
+ })
35
+
36
+ state.raw.gametype = this.readString(reader)
37
+ const isStarsiege2009 = state.raw.gametype === 'Starsiege'
38
+ state.raw.version = this.readString(reader)
39
+ state.name = this.readString(reader)
40
+
41
+ if (isStarsiege2009) {
42
+ state.password = !!reader.uint(1)
43
+ state.raw.dedicated = !!reader.uint(1)
44
+ state.raw.dropInProgress = !!reader.uint(1)
45
+ state.raw.gameInProgress = !!reader.uint(1)
46
+ state.numplayers = reader.uint(4)
47
+ state.maxplayers = reader.uint(4)
48
+ state.raw.teamPlay = reader.uint(1)
49
+ state.map = this.readString(reader)
50
+ state.raw.cpuSpeed = reader.uint(2)
51
+ state.raw.factoryVeh = reader.uint(1)
52
+ state.raw.allowTecmix = reader.uint(1)
53
+ state.raw.spawnLimit = reader.uint(4)
54
+ state.raw.fragLimit = reader.uint(4)
55
+ state.raw.timeLimit = reader.uint(4)
56
+ state.raw.techLimit = reader.uint(4)
57
+ state.raw.combatLimit = reader.uint(4)
58
+ state.raw.massLimit = reader.uint(4)
59
+ state.raw.playersSent = reader.uint(4)
60
+ const teams = { 1: 'yellow', 2: 'blue', 4: 'red', 8: 'purple' }
61
+ while (!reader.done()) {
62
+ const player = {}
63
+ player.name = this.readString(reader)
64
+ const teamId = reader.uint(1)
65
+ const team = teams[teamId]
66
+ if (team) player.team = teams[teamId]
67
+ }
68
+ return
69
+ }
70
+
71
+ state.raw.dedicated = !!reader.uint(1)
72
+ state.password = !!reader.uint(1)
73
+ state.raw.playerCount = reader.uint(1)
74
+ state.maxplayers = reader.uint(1)
75
+ state.raw.cpuSpeed = reader.uint(2)
76
+ state.raw.mod = this.readString(reader)
77
+ state.raw.type = this.readString(reader)
78
+ state.map = this.readString(reader)
79
+ state.raw.motd = this.readString(reader)
80
+ state.raw.teamCount = reader.uint(1)
81
+
82
+ const teamFields = this.readFieldList(reader)
83
+ const playerFields = this.readFieldList(reader)
84
+
85
+ state.raw.teams = []
86
+ for (let i = 0; i < state.raw.teamCount; i++) {
87
+ const teamName = this.readString(reader)
88
+ const teamValues = this.readValues(reader)
89
+
90
+ const teamInfo = {}
91
+ for (let i = 0; i < teamValues.length && i < teamFields.length; i++) {
92
+ let key = teamFields[i]
93
+ let value = teamValues[i]
94
+ if (key === 'ultra_base') key = 'name'
95
+ if (value === '%t') value = teamName
96
+ if (['score', 'players'].includes(key)) value = parseInt(value)
97
+ teamInfo[key] = value
98
+ }
99
+ state.raw.teams.push(teamInfo)
100
+ }
101
+
102
+ for (let i = 0; i < state.raw.playerCount; i++) {
103
+ const ping = reader.uint(1) * 4
104
+ const packetLoss = reader.uint(1)
105
+ const teamNum = reader.uint(1)
106
+ const name = this.readString(reader)
107
+ const playerValues = this.readValues(reader)
108
+
109
+ const playerInfo = {}
110
+ for (let i = 0; i < playerValues.length && i < playerFields.length; i++) {
111
+ const key = playerFields[i]
112
+ let value = playerValues[i]
113
+ if (value === '%p') value = ping
114
+ if (value === '%l') value = packetLoss
115
+ if (value === '%t') value = teamNum
116
+ if (value === '%n') value = name
117
+ if (['score', 'ping', 'pl', 'kills', 'lvl'].includes(key)) value = parseInt(value)
118
+ if (key === 'team') {
119
+ const teamId = parseInt(value)
120
+ if (teamId >= 0 && teamId < state.raw.teams.length && state.raw.teams[teamId].name) {
121
+ value = state.raw.teams[teamId].name
122
+ } else {
123
+ continue
124
+ }
125
+ }
126
+ playerInfo[key] = value
127
+ }
128
+ state.players.push(playerInfo)
129
+ }
130
+ }
131
+
132
+ readFieldList (reader) {
133
+ const str = this.readString(reader)
134
+ if (!str) return []
135
+ return ('?' + str)
136
+ .split('\t')
137
+ .map((a) => a.substring(1).trim().toLowerCase())
138
+ .map((a) => a === 'team name' ? 'name' : a)
139
+ .map((a) => a === 'player name' ? 'name' : a)
140
+ }
141
+
142
+ readValues (reader) {
143
+ const str = this.readString(reader)
144
+ if (!str) return []
145
+ return str
146
+ .split('\t')
147
+ .map((a) => a.trim())
148
+ }
149
+
150
+ readString (reader) {
151
+ return reader.pascalString(1)
152
+ }
153
+ }
@@ -0,0 +1,80 @@
1
+ import Core from './core.js'
2
+
3
+ /** Unsupported -- use at your own risk!! */
4
+
5
+ export default class tribes1master extends Core {
6
+ constructor () {
7
+ super()
8
+ this.encoding = 'latin1'
9
+ }
10
+
11
+ async run (state) {
12
+ const queryBuffer = Buffer.from([
13
+ 0x10, // standard header
14
+ 0x03, // dump servers
15
+ 0xff, // ask for all packets
16
+ 0x00, // junk
17
+ 0x01, 0x02 // challenge
18
+ ])
19
+
20
+ const parts = new Map()
21
+ let total = 0
22
+ const full = await this.udpSend(queryBuffer, (buffer) => {
23
+ const reader = this.reader(buffer)
24
+ const header = reader.uint(2)
25
+ if (header !== 0x0610) {
26
+ this.logger.debug('Header response does not match: ' + header.toString(16))
27
+ return
28
+ }
29
+ const num = reader.uint(1)
30
+ const t = reader.uint(1)
31
+ if (t <= 0 || (total > 0 && t !== total)) {
32
+ throw new Error('Conflicting packet total: ' + t)
33
+ }
34
+ total = t
35
+
36
+ if (num < 1 || num > total) {
37
+ this.logger.debug('Invalid packet number: ' + num + ' ' + total)
38
+ return
39
+ }
40
+ if (parts.has(num)) {
41
+ this.logger.debug('Duplicate part: ' + num)
42
+ return
43
+ }
44
+
45
+ reader.skip(2) // challenge (0x0201)
46
+ reader.skip(2) // always 0x6600
47
+ parts.set(num, reader.rest())
48
+
49
+ if (parts.size === total) {
50
+ const ordered = []
51
+ for (let i = 1; i <= total; i++) ordered.push(parts.get(i))
52
+ return Buffer.concat(ordered)
53
+ }
54
+ })
55
+
56
+ const fullReader = this.reader(full)
57
+ state.raw.name = this.readString(fullReader)
58
+ state.raw.motd = this.readString(fullReader)
59
+
60
+ state.raw.servers = []
61
+ while (!fullReader.done()) {
62
+ fullReader.skip(1) // junk ?
63
+ const count = fullReader.uint(1)
64
+ for (let i = 0; i < count; i++) {
65
+ const six = fullReader.uint(1)
66
+ if (six !== 6) {
67
+ throw new Error('Expecting 6')
68
+ }
69
+ const ip = fullReader.uint(4)
70
+ const port = fullReader.uint(2)
71
+ const ipStr = (ip & 255) + '.' + (ip >> 8 & 255) + '.' + (ip >> 16 & 255) + '.' + (ip >>> 24)
72
+ state.raw.servers.push(ipStr + ':' + port)
73
+ }
74
+ }
75
+ }
76
+
77
+ readString (reader) {
78
+ return reader.pascalString(1)
79
+ }
80
+ }
@@ -0,0 +1,150 @@
1
+ import Core from './core.js'
2
+
3
+ export default class unreal2 extends Core {
4
+ constructor () {
5
+ super()
6
+ this.encoding = 'latin1'
7
+ }
8
+
9
+ async run (state) {
10
+ let extraInfoReader
11
+ {
12
+ const b = await this.sendPacket(0, true)
13
+ const reader = this.reader(b)
14
+ state.raw.serverid = reader.uint(4)
15
+ state.raw.ip = this.readUnrealString(reader)
16
+ state.gamePort = reader.uint(4)
17
+ state.raw.queryport = reader.uint(4)
18
+ state.name = this.readUnrealString(reader, true)
19
+ state.map = this.readUnrealString(reader, true)
20
+ state.raw.gametype = this.readUnrealString(reader, true)
21
+ state.numplayers = reader.uint(4)
22
+ state.maxplayers = reader.uint(4)
23
+ this.logger.debug(log => {
24
+ log('UNREAL2 EXTRA INFO', reader.buffer.slice(reader.i))
25
+ })
26
+ extraInfoReader = reader
27
+ }
28
+
29
+ {
30
+ const b = await this.sendPacket(1, true)
31
+ const reader = this.reader(b)
32
+ state.raw.mutators = []
33
+ state.raw.rules = {}
34
+ while (!reader.done()) {
35
+ const key = this.readUnrealString(reader, true)
36
+ const value = this.readUnrealString(reader, true)
37
+ this.logger.debug(key + '=' + value)
38
+ if (key === 'Mutator' || key === 'mutator') {
39
+ state.raw.mutators.push(value)
40
+ } else if (key || value) {
41
+ if (Object.prototype.hasOwnProperty.call(state.raw.rules, key)) {
42
+ state.raw.rules[key] += ',' + value
43
+ } else {
44
+ state.raw.rules[key] = value
45
+ }
46
+ }
47
+ }
48
+ if ('GamePassword' in state.raw.rules) { state.password = state.raw.rules.GamePassword !== 'True' }
49
+ }
50
+
51
+ if (state.raw.mutators.includes('KillingFloorMut') ||
52
+ state.raw.rules['Num trader weapons'] ||
53
+ state.raw.rules['Server Version'] === '1065'
54
+ ) {
55
+ // Killing Floor
56
+ state.raw.wavecurrent = extraInfoReader.uint(4)
57
+ state.raw.wavetotal = extraInfoReader.uint(4)
58
+ state.raw.ping = extraInfoReader.uint(4)
59
+ state.raw.flags = extraInfoReader.uint(4)
60
+ state.raw.skillLevel = this.readUnrealString(extraInfoReader, true)
61
+ } else {
62
+ state.raw.ping = extraInfoReader.uint(4)
63
+ // These fields were added in later revisions of unreal engine
64
+ if (extraInfoReader.remaining() >= 8) {
65
+ state.raw.flags = extraInfoReader.uint(4)
66
+ state.raw.skill = this.readUnrealString(extraInfoReader, true)
67
+ }
68
+ }
69
+
70
+ {
71
+ const b = await this.sendPacket(2, false)
72
+ const reader = this.reader(b)
73
+
74
+ state.raw.scoreboard = {}
75
+ while (!reader.done()) {
76
+ const player = {}
77
+ player.id = reader.uint(4)
78
+ player.name = this.readUnrealString(reader, true)
79
+ player.ping = reader.uint(4)
80
+ player.score = reader.int(4)
81
+ player.statsId = reader.uint(4)
82
+ this.logger.debug(player)
83
+
84
+ if (!player.id) {
85
+ state.raw.scoreboard[player.name] = player.score
86
+ } else if (!player.ping) {
87
+ state.bots.push(player)
88
+ } else {
89
+ state.players.push(player)
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ readUnrealString (reader, stripColor) {
96
+ let length = reader.uint(1); let ucs2 = false
97
+ if (length >= 0x80) {
98
+ // This is flagged as a UCS-2 String
99
+ length = (length & 0x7f) * 2
100
+ ucs2 = true
101
+
102
+ // For UCS-2 strings, some unreal 2 games randomly insert an extra 0x01 here,
103
+ // not included in the length. Skip it if present (hopefully this never happens legitimately)
104
+ const peek = reader.uint(1)
105
+ if (peek !== 1) reader.skip(-1)
106
+
107
+ this.logger.debug(log => {
108
+ log('UCS2 STRING')
109
+ log('UCS2 Length: ' + length)
110
+ log(reader.buffer.slice(reader.i, reader.i + length))
111
+ })
112
+ }
113
+
114
+ let out = ''
115
+ if (ucs2) {
116
+ out = reader.string({ encoding: 'ucs2', length })
117
+ this.logger.debug('UCS2 String decoded: ' + out)
118
+ } else if (length > 0) {
119
+ out = reader.string()
120
+ }
121
+
122
+ // Sometimes the string has a null at the end (included with the length)
123
+ // Strip it if present
124
+ if (out.charCodeAt(out.length - 1) === 0) {
125
+ out = out.substring(0, out.length - 1)
126
+ }
127
+
128
+ if (stripColor) {
129
+ out = out.replace(/\x1b...|[\x00-\x1a]/gus, '')
130
+ }
131
+
132
+ return out
133
+ }
134
+
135
+ async sendPacket (type, required) {
136
+ const outbuffer = Buffer.from([0x79, 0, 0, 0, type])
137
+
138
+ const packets = []
139
+ return await this.udpSend(outbuffer, (buffer) => {
140
+ const reader = this.reader(buffer)
141
+ reader.uint(4) // header
142
+ const iType = reader.uint(1)
143
+ if (iType !== type) return
144
+ packets.push(reader.rest())
145
+ }, () => {
146
+ if (!packets.length && required) return
147
+ return Buffer.concat(packets)
148
+ })
149
+ }
150
+ }
@@ -0,0 +1,45 @@
1
+ import gamespy3 from './gamespy3.js'
2
+
3
+ export default class ut3 extends gamespy3 {
4
+ async run (state) {
5
+ await super.run(state)
6
+
7
+ this.translate(state.raw, {
8
+ mapname: false,
9
+ p1073741825: 'map',
10
+ p1073741826: 'gametype',
11
+ p1073741827: 'servername',
12
+ p1073741828: 'custom_mutators',
13
+ gamemode: 'joininprogress',
14
+ s32779: 'gamemode',
15
+ s0: 'bot_skill',
16
+ s6: 'pure_server',
17
+ s7: 'password',
18
+ s8: 'vs_bots',
19
+ s10: 'force_respawn',
20
+ p268435704: 'frag_limit',
21
+ p268435705: 'time_limit',
22
+ p268435703: 'numbots',
23
+ p268435717: 'stock_mutators',
24
+ p1073741829: 'stock_mutators',
25
+ s1: false,
26
+ s9: false,
27
+ s11: false,
28
+ s12: false,
29
+ s13: false,
30
+ s14: false,
31
+ p268435706: false,
32
+ p268435968: false,
33
+ p268435969: false
34
+ })
35
+
36
+ const split = (a) => {
37
+ let s = a.split('\x1c')
38
+ s = s.filter((e) => { return e })
39
+ return s
40
+ }
41
+ if ('custom_mutators' in state.raw) state.raw.custom_mutators = split(state.raw.custom_mutators)
42
+ if ('stock_mutators' in state.raw) state.raw.stock_mutators = split(state.raw.stock_mutators)
43
+ if ('map' in state.raw) state.map = state.raw.map
44
+ }
45
+ }