forked from gamedig/node-gamedig
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gamespy2.js
142 lines (123 loc) · 4.16 KB
/
gamespy2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import Core from './core.js'
export default class gamespy2 extends Core {
constructor () {
super()
this.encoding = 'latin1'
this.byteorder = 'be'
}
async run (state) {
// Parse info
{
const body = await this.sendPacket([0xff, 0, 0])
const reader = this.reader(body)
while (!reader.done()) {
const key = reader.string()
const value = reader.string()
if (!key) break
state.raw[key] = value
}
if ('hostname' in state.raw) state.name = state.raw.hostname
if ('mapname' in state.raw) state.map = state.raw.mapname
if (this.trueTest(state.raw.password)) state.password = true
if ('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers)
if ('hostport' in state.raw) state.gamePort = parseInt(state.raw.hostport)
if ('gamever' in state.raw) state.version = state.raw.gamever
}
// Parse players
{
const body = await this.sendPacket([0, 0xff, 0])
const reader = this.reader(body)
for (const rawPlayer of this.readFieldData(reader)) {
state.players.push(rawPlayer)
}
if ('numplayers' in state.raw) state.numplayers = parseInt(state.raw.numplayers)
else state.numplayers = state.players.length
}
// Parse teams
{
const body = await this.sendPacket([0, 0, 0xff])
const reader = this.reader(body)
state.raw.teams = this.readFieldData(reader)
}
// Special case for america's army 1 and 2
// both use gamename = "armygame"
if (state.raw.gamename === 'armygame') {
const stripColor = (str) => {
// uses unreal 2 color codes
return this.options.stripColors ? str.replace(/\x1b...|[\x00-\x1a]/g, '') : str
}
state.name = stripColor(state.name)
state.map = stripColor(state.map)
for (const key of Object.keys(state.raw)) {
if (typeof state.raw[key] === 'string') {
state.raw[key] = stripColor(state.raw[key])
}
}
for (const player of state.players) {
if (!('name' in player)) continue
player.name = stripColor(player.name)
}
}
}
async sendPacket (type) {
const request = Buffer.concat([
Buffer.from([0xfe, 0xfd, 0x00]), // gamespy2
Buffer.from([0x00, 0x00, 0x00, 0x01]), // ping ID
Buffer.from(type)
])
return await this.udpSend(request, buffer => {
const reader = this.reader(buffer)
const header = reader.uint(1)
if (header !== 0) return
const pingId = reader.uint(4)
if (pingId !== 1) return
return reader.rest()
})
}
readFieldData (reader) {
reader.uint(1) // always 0
const count = reader.uint(1) // number of rows in this data
// some games omit the count byte entirely if it's 0 or at random (like americas army)
// Luckily, count should always be <64, and ascii characters will typically be >64,
// so we can detect this.
if (count > 64) {
reader.skip(-1)
this.logger.debug('Detected missing count byte, rewinding by 1')
} else {
this.logger.debug('Detected row count: ' + count)
}
this.logger.debug(() => 'Reading fields, starting at: ' + reader.rest())
const fields = []
while (!reader.done()) {
const field = reader.string()
if (!field) break
fields.push(field)
this.logger.debug('field:' + field)
}
if (!fields.length) return []
const units = []
while (!reader.done()) {
const unit = {}
for (let index = 0; index < fields.length; index++) {
let key = fields[index]
let value = reader.string()
if (!value && index === 0) return units
this.logger.debug('value:' + value)
// many fields end with "_"
if (key.endsWith('_')) {
key = key.slice(0, -1)
}
if (key === 'player') key = 'name'
else if (key === 'team_t') key = 'name'
else if (key === 'tickets_t') key = 'tickets'
if (['score', 'deaths', 'ping', 'team', 'kills', 'tickets'].includes(key)) {
if (value === '') continue
value = parseInt(value)
}
unit[key] = value
}
units.push(unit)
}
return units
}
}