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.
- package/GAMES_LIST.md +453 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/bin/gamedig.js +79 -0
- package/lib/DnsResolver.js +76 -0
- package/lib/GlobalUdpSocket.js +69 -0
- package/lib/HexUtil.js +20 -0
- package/lib/Logger.js +45 -0
- package/lib/Promises.js +18 -0
- package/lib/ProtocolResolver.js +7 -0
- package/lib/QueryRunner.js +95 -0
- package/lib/Results.js +32 -0
- package/lib/game-resolver.js +17 -0
- package/lib/gamedig.js +23 -0
- package/lib/games.js +2747 -0
- package/lib/index.js +5 -0
- package/lib/reader.js +172 -0
- package/package.json +74 -0
- package/protocols/armagetron.js +65 -0
- package/protocols/asa.js +12 -0
- package/protocols/ase.js +45 -0
- package/protocols/assettocorsa.js +40 -0
- package/protocols/battlefield.js +162 -0
- package/protocols/beammp.js +32 -0
- package/protocols/beammpmaster.js +17 -0
- package/protocols/buildandshoot.js +55 -0
- package/protocols/core.js +349 -0
- package/protocols/cs2d.js +65 -0
- package/protocols/dayz.js +196 -0
- package/protocols/discord.js +29 -0
- package/protocols/doom3.js +148 -0
- package/protocols/eco.js +20 -0
- package/protocols/eldewrito.js +21 -0
- package/protocols/epic.js +95 -0
- package/protocols/ffow.js +38 -0
- package/protocols/fivem.js +33 -0
- package/protocols/gamespy1.js +181 -0
- package/protocols/gamespy2.js +144 -0
- package/protocols/gamespy3.js +197 -0
- package/protocols/geneshift.js +46 -0
- package/protocols/goldsrc.js +8 -0
- package/protocols/hexen2.js +14 -0
- package/protocols/index.js +61 -0
- package/protocols/jc2mp.js +16 -0
- package/protocols/kspdmp.js +28 -0
- package/protocols/mafia2mp.js +41 -0
- package/protocols/mafia2online.js +9 -0
- package/protocols/minecraft.js +102 -0
- package/protocols/minecraftbedrock.js +72 -0
- package/protocols/minecraftvanilla.js +87 -0
- package/protocols/mumble.js +39 -0
- package/protocols/mumbleping.js +24 -0
- package/protocols/nadeo.js +86 -0
- package/protocols/openttd.js +127 -0
- package/protocols/quake1.js +9 -0
- package/protocols/quake2.js +88 -0
- package/protocols/quake3.js +24 -0
- package/protocols/rfactor.js +69 -0
- package/protocols/samp.js +102 -0
- package/protocols/savage2.js +25 -0
- package/protocols/starmade.js +67 -0
- package/protocols/starsiege.js +10 -0
- package/protocols/teamspeak2.js +71 -0
- package/protocols/teamspeak3.js +69 -0
- package/protocols/terraria.js +24 -0
- package/protocols/tribes1.js +153 -0
- package/protocols/tribes1master.js +80 -0
- package/protocols/unreal2.js +150 -0
- package/protocols/ut3.js +45 -0
- package/protocols/valve.js +455 -0
- package/protocols/vcmp.js +10 -0
- package/protocols/ventrilo.js +237 -0
- package/protocols/warsow.js +13 -0
package/lib/index.js
ADDED
package/lib/reader.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import Iconv from 'iconv-lite'
|
|
2
|
+
import Long from 'long'
|
|
3
|
+
import { Buffer } from 'node:buffer'
|
|
4
|
+
import Varint from 'varint'
|
|
5
|
+
|
|
6
|
+
function readUInt64BE (buffer, offset) {
|
|
7
|
+
const high = buffer.readUInt32BE(offset)
|
|
8
|
+
const low = buffer.readUInt32BE(offset + 4)
|
|
9
|
+
return new Long(low, high, true)
|
|
10
|
+
}
|
|
11
|
+
function readUInt64LE (buffer, offset) {
|
|
12
|
+
const low = buffer.readUInt32LE(offset)
|
|
13
|
+
const high = buffer.readUInt32LE(offset + 4)
|
|
14
|
+
return new Long(low, high, true)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class Reader {
|
|
18
|
+
/**
|
|
19
|
+
* @param {Core} query
|
|
20
|
+
* @param {Buffer} buffer
|
|
21
|
+
**/
|
|
22
|
+
constructor (query, buffer) {
|
|
23
|
+
this.defaultEncoding = query.options.encoding || query.encoding
|
|
24
|
+
this.defaultDelimiter = query.delimiter
|
|
25
|
+
this.defaultByteOrder = query.byteorder
|
|
26
|
+
this.buffer = buffer
|
|
27
|
+
this.i = 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setOffset (offset) {
|
|
31
|
+
this.i = offset
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
offset () {
|
|
35
|
+
return this.i
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
skip (i) {
|
|
39
|
+
this.i += i
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pascalString (bytesForSize, adjustment = 0) {
|
|
43
|
+
const length = this.uint(bytesForSize) + adjustment
|
|
44
|
+
return this.string(length)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
string (arg) {
|
|
48
|
+
let encoding = this.defaultEncoding
|
|
49
|
+
let length = null
|
|
50
|
+
let delimiter = this.defaultDelimiter
|
|
51
|
+
|
|
52
|
+
if (typeof arg === 'string') delimiter = arg
|
|
53
|
+
else if (typeof arg === 'number') length = arg
|
|
54
|
+
else if (typeof arg === 'object') {
|
|
55
|
+
if ('encoding' in arg) encoding = arg.encoding
|
|
56
|
+
if ('length' in arg) length = arg.length
|
|
57
|
+
if ('delimiter' in arg) delimiter = arg.delimiter
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (encoding === 'latin1') encoding = 'win1252'
|
|
61
|
+
|
|
62
|
+
const start = this.i
|
|
63
|
+
let end = start
|
|
64
|
+
if (length === null) {
|
|
65
|
+
// terminated by the delimiter
|
|
66
|
+
let delim = delimiter
|
|
67
|
+
if (typeof delim === 'string') delim = delim.charCodeAt(0)
|
|
68
|
+
while (true) {
|
|
69
|
+
if (end >= this.buffer.length) {
|
|
70
|
+
end = this.buffer.length
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
if (this.buffer.readUInt8(end) === delim) break
|
|
74
|
+
end++
|
|
75
|
+
}
|
|
76
|
+
this.i = end + 1
|
|
77
|
+
} else if (length <= 0) {
|
|
78
|
+
return ''
|
|
79
|
+
} else {
|
|
80
|
+
end = start + length
|
|
81
|
+
if (end >= this.buffer.length) {
|
|
82
|
+
end = this.buffer.length
|
|
83
|
+
}
|
|
84
|
+
this.i = end
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const slice = this.buffer.slice(start, end)
|
|
88
|
+
const enc = encoding
|
|
89
|
+
if (enc === 'utf8' || enc === 'ucs2' || enc === 'binary') {
|
|
90
|
+
return slice.toString(enc)
|
|
91
|
+
} else {
|
|
92
|
+
return Iconv.decode(slice, enc)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
int (bytes) {
|
|
97
|
+
let r = 0
|
|
98
|
+
if (this.remaining() >= bytes) {
|
|
99
|
+
if (this.defaultByteOrder === 'be') {
|
|
100
|
+
if (bytes === 1) r = this.buffer.readInt8(this.i)
|
|
101
|
+
else if (bytes === 2) r = this.buffer.readInt16BE(this.i)
|
|
102
|
+
else if (bytes === 4) r = this.buffer.readInt32BE(this.i)
|
|
103
|
+
} else {
|
|
104
|
+
if (bytes === 1) r = this.buffer.readInt8(this.i)
|
|
105
|
+
else if (bytes === 2) r = this.buffer.readInt16LE(this.i)
|
|
106
|
+
else if (bytes === 4) r = this.buffer.readInt32LE(this.i)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.i += bytes
|
|
110
|
+
return r
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @returns {number} */
|
|
114
|
+
uint (bytes) {
|
|
115
|
+
let r = 0
|
|
116
|
+
if (this.remaining() >= bytes) {
|
|
117
|
+
if (this.defaultByteOrder === 'be') {
|
|
118
|
+
if (bytes === 1) r = this.buffer.readUInt8(this.i)
|
|
119
|
+
else if (bytes === 2) r = this.buffer.readUInt16BE(this.i)
|
|
120
|
+
else if (bytes === 4) r = this.buffer.readUInt32BE(this.i)
|
|
121
|
+
else if (bytes === 8) r = readUInt64BE(this.buffer, this.i)
|
|
122
|
+
} else {
|
|
123
|
+
if (bytes === 1) r = this.buffer.readUInt8(this.i)
|
|
124
|
+
else if (bytes === 2) r = this.buffer.readUInt16LE(this.i)
|
|
125
|
+
else if (bytes === 4) r = this.buffer.readUInt32LE(this.i)
|
|
126
|
+
else if (bytes === 8) r = readUInt64LE(this.buffer, this.i)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.i += bytes
|
|
130
|
+
return r
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
float () {
|
|
134
|
+
let r = 0
|
|
135
|
+
if (this.remaining() >= 4) {
|
|
136
|
+
if (this.defaultByteOrder === 'be') r = this.buffer.readFloatBE(this.i)
|
|
137
|
+
else r = this.buffer.readFloatLE(this.i)
|
|
138
|
+
}
|
|
139
|
+
this.i += 4
|
|
140
|
+
return r
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
varint () {
|
|
144
|
+
const out = Varint.decode(this.buffer, this.i)
|
|
145
|
+
this.i += Varint.decode.bytes
|
|
146
|
+
return out
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @returns Buffer */
|
|
150
|
+
part (bytes) {
|
|
151
|
+
let r
|
|
152
|
+
if (this.remaining() >= bytes) {
|
|
153
|
+
r = this.buffer.slice(this.i, this.i + bytes)
|
|
154
|
+
} else {
|
|
155
|
+
r = Buffer.from([])
|
|
156
|
+
}
|
|
157
|
+
this.i += bytes
|
|
158
|
+
return r
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
remaining () {
|
|
162
|
+
return this.buffer.length - this.i
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
rest () {
|
|
166
|
+
return this.buffer.slice(this.i)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
done () {
|
|
170
|
+
return this.i >= this.buffer.length
|
|
171
|
+
}
|
|
172
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gamedigz",
|
|
3
|
+
"description": "Query for the status of any game server in Node.JS",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"lint:check": "eslint .",
|
|
6
|
+
"lint:fix": "eslint --fix ."
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"srcds",
|
|
10
|
+
"query",
|
|
11
|
+
"game",
|
|
12
|
+
"utility",
|
|
13
|
+
"util",
|
|
14
|
+
"server",
|
|
15
|
+
"gameserver",
|
|
16
|
+
"game-server-query",
|
|
17
|
+
"game server query",
|
|
18
|
+
"server query",
|
|
19
|
+
"game server",
|
|
20
|
+
"gameserverquery",
|
|
21
|
+
"serverquery",
|
|
22
|
+
"terraria",
|
|
23
|
+
"counter strike",
|
|
24
|
+
"csgo",
|
|
25
|
+
"minecraft"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "lib/index.js",
|
|
29
|
+
"author": "GameDig Contributors",
|
|
30
|
+
"version": "0.1.0",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/gamedig/node-gamedig.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/gamedig/node-gamedig/issues"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=16.20.0"
|
|
41
|
+
},
|
|
42
|
+
"bin": {
|
|
43
|
+
"gamedig": "bin/gamedig.js"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"bin/gamedig.js",
|
|
47
|
+
"lib/",
|
|
48
|
+
"protocols/",
|
|
49
|
+
"games.txt",
|
|
50
|
+
"LICENSE",
|
|
51
|
+
"GAMES_LIST.md",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"cheerio": "^1.0.0-rc.12",
|
|
56
|
+
"gbxremote": "^0.2.1",
|
|
57
|
+
"got": "^13.0.0",
|
|
58
|
+
"iconv-lite": "^0.6.3",
|
|
59
|
+
"long": "^5.2.3",
|
|
60
|
+
"minimist": "^1.2.8",
|
|
61
|
+
"punycode": "^2.3.0",
|
|
62
|
+
"seek-bzip": "^2.0.0",
|
|
63
|
+
"varint": "^6.0.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/cheerio": "^0.22.31",
|
|
67
|
+
"@types/node": "^16.18.58",
|
|
68
|
+
"eslint": "^8.49.0",
|
|
69
|
+
"eslint-config-standard": "^17.1.0",
|
|
70
|
+
"eslint-plugin-import": "^2.28.1",
|
|
71
|
+
"eslint-plugin-n": "15.7.0",
|
|
72
|
+
"eslint-plugin-promise": "^6.1.1"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class armagetron extends Core {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
this.encoding = 'latin1'
|
|
7
|
+
this.byteorder = 'be'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async run (state) {
|
|
11
|
+
const b = Buffer.from([0, 0x35, 0, 0, 0, 0, 0, 0x11])
|
|
12
|
+
|
|
13
|
+
const buffer = await this.udpSend(b, b => b)
|
|
14
|
+
const reader = this.reader(buffer)
|
|
15
|
+
|
|
16
|
+
reader.skip(6)
|
|
17
|
+
|
|
18
|
+
state.gamePort = this.readUInt(reader)
|
|
19
|
+
state.raw.hostname = this.readString(reader)
|
|
20
|
+
state.name = this.stripColorCodes(this.readString(reader))
|
|
21
|
+
state.numplayers = this.readUInt(reader)
|
|
22
|
+
state.raw.versionmin = this.readUInt(reader)
|
|
23
|
+
state.raw.versionmax = this.readUInt(reader)
|
|
24
|
+
state.raw.version = this.readString(reader)
|
|
25
|
+
state.maxplayers = this.readUInt(reader)
|
|
26
|
+
|
|
27
|
+
const players = this.readString(reader)
|
|
28
|
+
const list = players.split('\n')
|
|
29
|
+
for (const name of list) {
|
|
30
|
+
if (!name) continue
|
|
31
|
+
state.players.push({
|
|
32
|
+
name: this.stripColorCodes(name)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
state.raw.options = this.stripColorCodes(this.readString(reader))
|
|
37
|
+
state.raw.uri = this.readString(reader)
|
|
38
|
+
state.raw.globalids = this.readString(reader)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
readUInt (reader) {
|
|
42
|
+
const a = reader.uint(2)
|
|
43
|
+
const b = reader.uint(2)
|
|
44
|
+
return (b << 16) + a
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
readString (reader) {
|
|
48
|
+
const len = reader.uint(2)
|
|
49
|
+
if (!len) return ''
|
|
50
|
+
|
|
51
|
+
let out = ''
|
|
52
|
+
for (let i = 0; i < len; i += 2) {
|
|
53
|
+
const hi = reader.uint(1)
|
|
54
|
+
const lo = reader.uint(1)
|
|
55
|
+
if (i + 1 < len) out += String.fromCharCode(lo)
|
|
56
|
+
if (i + 2 < len) out += String.fromCharCode(hi)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
stripColorCodes (str) {
|
|
63
|
+
return str.replace(/0x[0-9a-f]{6}/g, '')
|
|
64
|
+
}
|
|
65
|
+
}
|
package/protocols/asa.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Epic from './epic.js'
|
|
2
|
+
|
|
3
|
+
export default class asa extends Epic {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
|
|
7
|
+
// OAuth2 credentials extracted from ARK: Survival Ascended files.
|
|
8
|
+
this.clientId = 'xyza7891muomRmynIIHaJB9COBKkwj6n'
|
|
9
|
+
this.clientSecret = 'PP5UGxysEieNfSrEicaD1N2Bb3TdXuD7xHYcsdUHZ7s'
|
|
10
|
+
this.deploymentId = 'ad9a8feffb3b4b2ca315546f038c3ae2'
|
|
11
|
+
}
|
|
12
|
+
}
|
package/protocols/ase.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class ase extends Core {
|
|
4
|
+
async run (state) {
|
|
5
|
+
const buffer = await this.udpSend('s', (buffer) => {
|
|
6
|
+
const reader = this.reader(buffer)
|
|
7
|
+
const header = reader.string(4)
|
|
8
|
+
if (header === 'EYE1') return reader.rest()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const reader = this.reader(buffer)
|
|
12
|
+
state.raw.gamename = this.readString(reader)
|
|
13
|
+
state.gamePort = parseInt(this.readString(reader))
|
|
14
|
+
state.name = this.readString(reader)
|
|
15
|
+
state.raw.gametype = this.readString(reader)
|
|
16
|
+
state.map = this.readString(reader)
|
|
17
|
+
state.raw.version = this.readString(reader)
|
|
18
|
+
state.password = this.readString(reader) === '1'
|
|
19
|
+
state.numplayers = parseInt(this.readString(reader))
|
|
20
|
+
state.maxplayers = parseInt(this.readString(reader))
|
|
21
|
+
|
|
22
|
+
while (!reader.done()) {
|
|
23
|
+
const key = this.readString(reader)
|
|
24
|
+
if (!key) break
|
|
25
|
+
const value = this.readString(reader)
|
|
26
|
+
state.raw[key] = value
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
while (!reader.done()) {
|
|
30
|
+
const flags = reader.uint(1)
|
|
31
|
+
const player = {}
|
|
32
|
+
if (flags & 1) player.name = this.readString(reader)
|
|
33
|
+
if (flags & 2) player.team = this.readString(reader)
|
|
34
|
+
if (flags & 4) player.skin = this.readString(reader)
|
|
35
|
+
if (flags & 8) player.score = parseInt(this.readString(reader))
|
|
36
|
+
if (flags & 16) player.ping = parseInt(this.readString(reader))
|
|
37
|
+
if (flags & 32) player.time = parseInt(this.readString(reader))
|
|
38
|
+
state.players.push(player)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
readString (reader) {
|
|
43
|
+
return reader.pascalString(1, -1)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class assettocorsa extends Core {
|
|
4
|
+
async run (state) {
|
|
5
|
+
const serverInfo = await this.request({
|
|
6
|
+
url: `http://${this.options.address}:${this.options.port}/INFO`,
|
|
7
|
+
responseType: 'json'
|
|
8
|
+
})
|
|
9
|
+
const carInfo = await this.request({
|
|
10
|
+
url: `http://${this.options.address}:${this.options.port}/JSON|${parseInt(Math.random() * 999999999999999, 10)}`,
|
|
11
|
+
responseType: 'json'
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
if (!serverInfo || !carInfo || !carInfo.Cars) {
|
|
15
|
+
throw new Error('Query not successful')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
state.maxplayers = serverInfo.maxclients
|
|
19
|
+
state.name = serverInfo.name
|
|
20
|
+
state.map = serverInfo.track
|
|
21
|
+
state.password = serverInfo.pass
|
|
22
|
+
state.gamePort = serverInfo.port
|
|
23
|
+
state.raw.carInfo = carInfo.Cars
|
|
24
|
+
state.raw.serverInfo = serverInfo
|
|
25
|
+
|
|
26
|
+
for (const car of carInfo.Cars) {
|
|
27
|
+
if (car.IsConnected) {
|
|
28
|
+
state.players.push({
|
|
29
|
+
name: car.DriverName,
|
|
30
|
+
car: car.Model,
|
|
31
|
+
skin: car.Skin,
|
|
32
|
+
nation: car.DriverNation,
|
|
33
|
+
team: car.DriverTeam
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
state.numplayers = carInfo.Cars.length
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class battlefield extends Core {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
this.encoding = 'latin1'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async run (state) {
|
|
10
|
+
await this.withTcp(async socket => {
|
|
11
|
+
{
|
|
12
|
+
const data = await this.query(socket, ['serverInfo'])
|
|
13
|
+
state.name = data.shift()
|
|
14
|
+
state.numplayers = parseInt(data.shift())
|
|
15
|
+
state.maxplayers = parseInt(data.shift())
|
|
16
|
+
state.raw.gametype = data.shift()
|
|
17
|
+
state.map = data.shift()
|
|
18
|
+
state.raw.roundsplayed = parseInt(data.shift())
|
|
19
|
+
state.raw.roundstotal = parseInt(data.shift())
|
|
20
|
+
|
|
21
|
+
const teamCount = data.shift()
|
|
22
|
+
state.raw.teams = []
|
|
23
|
+
for (let i = 0; i < teamCount; i++) {
|
|
24
|
+
const tickets = parseFloat(data.shift())
|
|
25
|
+
state.raw.teams.push({
|
|
26
|
+
tickets
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
state.raw.targetscore = parseInt(data.shift())
|
|
31
|
+
state.raw.status = data.shift()
|
|
32
|
+
|
|
33
|
+
// Seems like the fields end at random places beyond this point
|
|
34
|
+
// depending on the server version
|
|
35
|
+
|
|
36
|
+
if (data.length) state.raw.ranked = (data.shift() === 'true')
|
|
37
|
+
if (data.length) state.raw.punkbuster = (data.shift() === 'true')
|
|
38
|
+
if (data.length) state.password = (data.shift() === 'true')
|
|
39
|
+
if (data.length) state.raw.uptime = parseInt(data.shift())
|
|
40
|
+
if (data.length) state.raw.roundtime = parseInt(data.shift())
|
|
41
|
+
|
|
42
|
+
const isBadCompany2 = data[0] === 'BC2'
|
|
43
|
+
if (isBadCompany2) {
|
|
44
|
+
if (data.length) data.shift()
|
|
45
|
+
if (data.length) data.shift()
|
|
46
|
+
}
|
|
47
|
+
if (data.length) {
|
|
48
|
+
state.raw.ip = data.shift()
|
|
49
|
+
const split = state.raw.ip.split(':')
|
|
50
|
+
state.gameHost = split[0]
|
|
51
|
+
state.gamePort = split[1]
|
|
52
|
+
} else {
|
|
53
|
+
// best guess if the server doesn't tell us what the server port is
|
|
54
|
+
// these are just the default game ports for different default query ports
|
|
55
|
+
if (this.options.port === 48888) state.gamePort = 7673
|
|
56
|
+
if (this.options.port === 22000) state.gamePort = 25200
|
|
57
|
+
}
|
|
58
|
+
if (data.length) state.raw.punkbusterversion = data.shift()
|
|
59
|
+
if (data.length) state.raw.joinqueue = (data.shift() === 'true')
|
|
60
|
+
if (data.length) state.raw.region = data.shift()
|
|
61
|
+
if (data.length) state.raw.pingsite = data.shift()
|
|
62
|
+
if (data.length) state.raw.country = data.shift()
|
|
63
|
+
if (data.length) state.raw.quickmatch = (data.shift() === 'true')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
const data = await this.query(socket, ['version'])
|
|
68
|
+
data.shift()
|
|
69
|
+
state.raw.version = data.shift()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
const data = await this.query(socket, ['listPlayers', 'all'])
|
|
74
|
+
const fieldCount = parseInt(data.shift())
|
|
75
|
+
const fields = []
|
|
76
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
77
|
+
fields.push(data.shift())
|
|
78
|
+
}
|
|
79
|
+
const numplayers = data.shift()
|
|
80
|
+
for (let i = 0; i < numplayers; i++) {
|
|
81
|
+
const player = {}
|
|
82
|
+
for (let key of fields) {
|
|
83
|
+
let value = data.shift()
|
|
84
|
+
|
|
85
|
+
if (key === 'teamId') key = 'team'
|
|
86
|
+
else if (key === 'squadId') key = 'squad'
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
key === 'kills' ||
|
|
90
|
+
key === 'deaths' ||
|
|
91
|
+
key === 'score' ||
|
|
92
|
+
key === 'rank' ||
|
|
93
|
+
key === 'team' ||
|
|
94
|
+
key === 'squad' ||
|
|
95
|
+
key === 'ping' ||
|
|
96
|
+
key === 'type'
|
|
97
|
+
) {
|
|
98
|
+
value = parseInt(value)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
player[key] = value
|
|
102
|
+
}
|
|
103
|
+
state.players.push(player)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async query (socket, params) {
|
|
110
|
+
const outPacket = this.buildPacket(params)
|
|
111
|
+
return await this.tcpSend(socket, outPacket, (data) => {
|
|
112
|
+
const decoded = this.decodePacket(data)
|
|
113
|
+
if (decoded) {
|
|
114
|
+
this.logger.debug(decoded)
|
|
115
|
+
if (decoded.shift() !== 'OK') throw new Error('Missing OK')
|
|
116
|
+
return decoded
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
buildPacket (params) {
|
|
122
|
+
const paramBuffers = []
|
|
123
|
+
for (const param of params) {
|
|
124
|
+
paramBuffers.push(Buffer.from(param, 'utf8'))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let totalLength = 12
|
|
128
|
+
for (const paramBuffer of paramBuffers) {
|
|
129
|
+
totalLength += paramBuffer.length + 1 + 4
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const b = Buffer.alloc(totalLength)
|
|
133
|
+
b.writeUInt32LE(0, 0)
|
|
134
|
+
b.writeUInt32LE(totalLength, 4)
|
|
135
|
+
b.writeUInt32LE(params.length, 8)
|
|
136
|
+
let offset = 12
|
|
137
|
+
for (const paramBuffer of paramBuffers) {
|
|
138
|
+
b.writeUInt32LE(paramBuffer.length, offset); offset += 4
|
|
139
|
+
paramBuffer.copy(b, offset); offset += paramBuffer.length
|
|
140
|
+
b.writeUInt8(0, offset); offset += 1
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return b
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
decodePacket (buffer) {
|
|
147
|
+
if (buffer.length < 8) return false
|
|
148
|
+
const reader = this.reader(buffer)
|
|
149
|
+
reader.uint(4) // header
|
|
150
|
+
const totalLength = reader.uint(4)
|
|
151
|
+
if (buffer.length < totalLength) return false
|
|
152
|
+
this.logger.debug('Expected ' + totalLength + ' bytes, have ' + buffer.length)
|
|
153
|
+
|
|
154
|
+
const paramCount = reader.uint(4)
|
|
155
|
+
const params = []
|
|
156
|
+
for (let i = 0; i < paramCount; i++) {
|
|
157
|
+
params.push(reader.pascalString(4))
|
|
158
|
+
reader.uint(1) // strNull
|
|
159
|
+
}
|
|
160
|
+
return params
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
import beammpmaster from './beammpmaster.js'
|
|
3
|
+
|
|
4
|
+
export default class beammp extends Core {
|
|
5
|
+
async run (state) {
|
|
6
|
+
const master = new beammpmaster()
|
|
7
|
+
master.options = this.options
|
|
8
|
+
const masterState = await master.runOnceSafe()
|
|
9
|
+
const servers = masterState.raw.servers
|
|
10
|
+
const server = servers.find(s => s.ip === this.options.host)
|
|
11
|
+
|
|
12
|
+
if (!server) {
|
|
13
|
+
throw new Error('Server not found in the master list')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
state.name = server.sname.replace(/\^./g, '')
|
|
17
|
+
state.map = server.map
|
|
18
|
+
state.password = server.password
|
|
19
|
+
state.numplayers = parseInt(server.players)
|
|
20
|
+
state.maxplayers = parseInt(server.players)
|
|
21
|
+
|
|
22
|
+
const players = server.playerslist.split(';')
|
|
23
|
+
if (players[players.length - 1] === '') {
|
|
24
|
+
players.pop()
|
|
25
|
+
}
|
|
26
|
+
players.forEach(player => {
|
|
27
|
+
state.players.push({ name: player })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
state.raw = server
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class beammpmaster extends Core {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
|
|
7
|
+
// Don't use the tcp ping probing
|
|
8
|
+
this.usedTcp = true
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run (state) {
|
|
12
|
+
state.raw.servers = await this.request({
|
|
13
|
+
url: 'https://backend.beammp.com/servers-info',
|
|
14
|
+
responseType: 'json'
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
import * as cheerio from 'cheerio'
|
|
3
|
+
|
|
4
|
+
export default class buildandshoot extends Core {
|
|
5
|
+
async run (state) {
|
|
6
|
+
const body = await this.request({
|
|
7
|
+
url: 'http://' + this.options.address + ':' + this.options.port + '/'
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
let m
|
|
11
|
+
|
|
12
|
+
m = body.match(/status server for (.*?)\.?[\r\n]/)
|
|
13
|
+
if (m) state.name = m[1]
|
|
14
|
+
|
|
15
|
+
m = body.match(/Current uptime: (\d+)/)
|
|
16
|
+
if (m) state.raw.uptime = m[1]
|
|
17
|
+
|
|
18
|
+
m = body.match(/currently running (.*?) by /)
|
|
19
|
+
if (m) state.map = m[1]
|
|
20
|
+
|
|
21
|
+
m = body.match(/Current players: (\d+)\/(\d+)/)
|
|
22
|
+
if (m) {
|
|
23
|
+
state.numplayers = parseInt(m[1])
|
|
24
|
+
state.maxplayers = m[2]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
m = body.match(/aos:\/\/[0-9]+:[0-9]+/)
|
|
28
|
+
if (m) {
|
|
29
|
+
state.connect = m[0]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const $ = cheerio.load(body)
|
|
33
|
+
$('#playerlist tbody tr').each((i, tr) => {
|
|
34
|
+
if (!$(tr).find('td').first().attr('colspan')) {
|
|
35
|
+
state.players.push({
|
|
36
|
+
name: $(tr).find('td').eq(2).text(),
|
|
37
|
+
ping: $(tr).find('td').eq(3).text().trim(),
|
|
38
|
+
team: $(tr).find('td').eq(4).text().toLowerCase(),
|
|
39
|
+
score: parseInt($(tr).find('td').eq(5).text())
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
/*
|
|
44
|
+
var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
|
|
45
|
+
if(m) {
|
|
46
|
+
var o1 = parseInt(m[1]);
|
|
47
|
+
var o2 = parseInt(m[2]);
|
|
48
|
+
var o3 = parseInt(m[3]);
|
|
49
|
+
var o4 = parseInt(m[4]);
|
|
50
|
+
var addr = o1+(o2<<8)+(o3<<16)+(o4<<24);
|
|
51
|
+
state.raw.url = 'aos://'+addr;
|
|
52
|
+
}
|
|
53
|
+
*/
|
|
54
|
+
}
|
|
55
|
+
}
|