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
|
@@ -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
|
+
}
|
package/protocols/ut3.js
ADDED
|
@@ -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
|
+
}
|