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,39 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class mumble extends Core {
|
|
4
|
+
async run (state) {
|
|
5
|
+
const json = await this.withTcp(async socket => {
|
|
6
|
+
return await this.tcpSend(socket, 'json', (buffer) => {
|
|
7
|
+
if (buffer.length < 10) return
|
|
8
|
+
const str = buffer.toString()
|
|
9
|
+
let json
|
|
10
|
+
try {
|
|
11
|
+
json = JSON.parse(str)
|
|
12
|
+
} catch (e) {
|
|
13
|
+
// probably not all here yet
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
return json
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
state.raw = json
|
|
21
|
+
state.name = json.name
|
|
22
|
+
state.gamePort = json.x_gtmurmur_connectport || 64738
|
|
23
|
+
|
|
24
|
+
let channelStack = [state.raw.root]
|
|
25
|
+
while (channelStack.length) {
|
|
26
|
+
const channel = channelStack.shift()
|
|
27
|
+
channel.description = this.cleanComment(channel.description)
|
|
28
|
+
channelStack = channelStack.concat(channel.channels)
|
|
29
|
+
for (const user of channel.users) {
|
|
30
|
+
user.comment = this.cleanComment(user.comment)
|
|
31
|
+
state.players.push(user)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
cleanComment (str) {
|
|
37
|
+
return str.replace(/<.*>/g, '')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class mumbleping extends Core {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
this.byteorder = 'be'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async run (state) {
|
|
10
|
+
const data = await this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', (buffer) => {
|
|
11
|
+
if (buffer.length >= 24) return buffer
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const reader = this.reader(data)
|
|
15
|
+
reader.skip(1)
|
|
16
|
+
state.raw.versionMajor = reader.uint(1)
|
|
17
|
+
state.raw.versionMinor = reader.uint(1)
|
|
18
|
+
state.raw.versionPatch = reader.uint(1)
|
|
19
|
+
reader.skip(8)
|
|
20
|
+
state.numplayers = reader.uint(4)
|
|
21
|
+
state.maxplayers = reader.uint(4)
|
|
22
|
+
state.raw.allowedbandwidth = reader.uint(4)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
import Promises from '../lib/Promises.js'
|
|
3
|
+
import * as gbxremote from 'gbxremote'
|
|
4
|
+
|
|
5
|
+
export default class nadeo extends Core {
|
|
6
|
+
async run (state) {
|
|
7
|
+
await this.withClient(async client => {
|
|
8
|
+
const start = Date.now()
|
|
9
|
+
await this.query(client, 'Authenticate', this.options.login, this.options.password)
|
|
10
|
+
this.registerRtt(Date.now() - start)
|
|
11
|
+
|
|
12
|
+
// const data = this.methodCall(client, 'GetStatus');
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
const results = await this.query(client, 'GetServerOptions')
|
|
16
|
+
state.name = this.stripColors(results.Name)
|
|
17
|
+
state.password = (results.Password !== 'No password')
|
|
18
|
+
state.maxplayers = results.CurrentMaxPlayers
|
|
19
|
+
state.raw.maxspectators = results.CurrentMaxSpectators
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
const results = await this.query(client, 'GetCurrentMapInfo')
|
|
24
|
+
state.map = this.stripColors(results.Name)
|
|
25
|
+
state.raw.mapUid = results.UId
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
const results = await this.query(client, 'GetCurrentGameInfo')
|
|
30
|
+
let gamemode = ''
|
|
31
|
+
const igm = results.GameMode
|
|
32
|
+
if (igm === 0) gamemode = 'Rounds'
|
|
33
|
+
if (igm === 1) gamemode = 'Time Attack'
|
|
34
|
+
if (igm === 2) gamemode = 'Team'
|
|
35
|
+
if (igm === 3) gamemode = 'Laps'
|
|
36
|
+
if (igm === 4) gamemode = 'Stunts'
|
|
37
|
+
if (igm === 5) gamemode = 'Cup'
|
|
38
|
+
state.raw.gametype = gamemode
|
|
39
|
+
state.raw.mapcount = results.NbChallenge
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
const results = await this.query(client, 'GetNextMapInfo')
|
|
44
|
+
state.raw.nextmapName = this.stripColors(results.Name)
|
|
45
|
+
state.raw.nextmapUid = results.UId
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (this.options.port === 5000) {
|
|
49
|
+
state.gamePort = 2350
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
state.raw.players = await this.query(client, 'GetPlayerList', 10000, 0)
|
|
53
|
+
for (const player of state.raw.players) {
|
|
54
|
+
state.players.push({
|
|
55
|
+
name: this.stripColors(player.Name || player.NickName)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
state.numplayers = state.players.length
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async withClient (fn) {
|
|
63
|
+
const socket = new gbxremote.Client(this.options.port, this.options.host)
|
|
64
|
+
try {
|
|
65
|
+
const connectPromise = socket.connect()
|
|
66
|
+
const timeoutPromise = Promises.createTimeout(this.options.socketTimeout, 'GBX Remote Opening')
|
|
67
|
+
await Promise.race([connectPromise, timeoutPromise, this.abortedPromise])
|
|
68
|
+
return await fn(socket)
|
|
69
|
+
} finally {
|
|
70
|
+
socket.terminate()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async query (client, ...cmdset) {
|
|
75
|
+
const cmd = cmdset[0]
|
|
76
|
+
const params = cmdset.slice(1)
|
|
77
|
+
|
|
78
|
+
const sentPromise = client.query(cmd, params)
|
|
79
|
+
const timeoutPromise = Promises.createTimeout(this.options.socketTimeout, 'GBX Method Call')
|
|
80
|
+
return await Promise.race([sentPromise, timeoutPromise, this.abortedPromise])
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
stripColors (str) {
|
|
84
|
+
return str.replace(/\$([0-9a-f]{3}|[a-z])/gi, '')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class openttd extends Core {
|
|
4
|
+
async run (state) {
|
|
5
|
+
{
|
|
6
|
+
const [reader, version] = await this.query(0, 1, 1, 4)
|
|
7
|
+
if (version >= 4) {
|
|
8
|
+
const numGrf = reader.uint(1)
|
|
9
|
+
state.raw.grfs = []
|
|
10
|
+
for (let i = 0; i < numGrf; i++) {
|
|
11
|
+
const grf = {}
|
|
12
|
+
grf.id = reader.part(4).toString('hex')
|
|
13
|
+
grf.md5 = reader.part(16).toString('hex')
|
|
14
|
+
state.raw.grfs.push(grf)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (version >= 3) {
|
|
18
|
+
state.raw.date_current = this.readDate(reader)
|
|
19
|
+
state.raw.date_start = this.readDate(reader)
|
|
20
|
+
}
|
|
21
|
+
if (version >= 2) {
|
|
22
|
+
state.raw.maxcompanies = reader.uint(1)
|
|
23
|
+
state.raw.numcompanies = reader.uint(1)
|
|
24
|
+
state.raw.maxspectators = reader.uint(1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
state.name = reader.string()
|
|
28
|
+
state.raw.version = reader.string()
|
|
29
|
+
|
|
30
|
+
state.raw.language = this.decode(
|
|
31
|
+
reader.uint(1),
|
|
32
|
+
['any', 'en', 'de', 'fr']
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
state.password = !!reader.uint(1)
|
|
36
|
+
state.maxplayers = reader.uint(1)
|
|
37
|
+
state.numplayers = reader.uint(1)
|
|
38
|
+
state.raw.numspectators = reader.uint(1)
|
|
39
|
+
state.map = reader.string()
|
|
40
|
+
state.raw.map_width = reader.uint(2)
|
|
41
|
+
state.raw.map_height = reader.uint(2)
|
|
42
|
+
|
|
43
|
+
state.raw.landscape = this.decode(
|
|
44
|
+
reader.uint(1),
|
|
45
|
+
['temperate', 'arctic', 'desert', 'toyland']
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
state.raw.dedicated = !!reader.uint(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
{
|
|
52
|
+
const [reader, version] = await this.query(2, 3, -1, -1)
|
|
53
|
+
// we don't know how to deal with companies outside version 6
|
|
54
|
+
if (version === 6) {
|
|
55
|
+
state.raw.companies = []
|
|
56
|
+
const numCompanies = reader.uint(1)
|
|
57
|
+
for (let iCompany = 0; iCompany < numCompanies; iCompany++) {
|
|
58
|
+
const company = {}
|
|
59
|
+
company.id = reader.uint(1)
|
|
60
|
+
company.name = reader.string()
|
|
61
|
+
company.year_start = reader.uint(4)
|
|
62
|
+
company.value = reader.uint(8).toString()
|
|
63
|
+
company.money = reader.uint(8).toString()
|
|
64
|
+
company.income = reader.uint(8).toString()
|
|
65
|
+
company.performance = reader.uint(2)
|
|
66
|
+
company.password = !!reader.uint(1)
|
|
67
|
+
|
|
68
|
+
const vehicleTypes = ['train', 'truck', 'bus', 'aircraft', 'ship']
|
|
69
|
+
const stationTypes = ['station', 'truckbay', 'busstation', 'airport', 'dock']
|
|
70
|
+
|
|
71
|
+
company.vehicles = {}
|
|
72
|
+
for (const type of vehicleTypes) {
|
|
73
|
+
company.vehicles[type] = reader.uint(2)
|
|
74
|
+
}
|
|
75
|
+
company.stations = {}
|
|
76
|
+
for (const type of stationTypes) {
|
|
77
|
+
company.stations[type] = reader.uint(2)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
company.clients = reader.string()
|
|
81
|
+
state.raw.companies.push(company)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async query (type, expected, minver, maxver) {
|
|
88
|
+
const b = Buffer.from([0x03, 0x00, type])
|
|
89
|
+
return await this.udpSend(b, (buffer) => {
|
|
90
|
+
const reader = this.reader(buffer)
|
|
91
|
+
|
|
92
|
+
const packetLen = reader.uint(2)
|
|
93
|
+
if (packetLen !== buffer.length) {
|
|
94
|
+
this.logger.debug('Invalid reported packet length: ' + packetLen + ' ' + buffer.length)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const packetType = reader.uint(1)
|
|
99
|
+
if (packetType !== expected) {
|
|
100
|
+
this.logger.debug('Unexpected response packet type: ' + packetType)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const protocolVersion = reader.uint(1)
|
|
105
|
+
if ((minver !== -1 && protocolVersion < minver) || (maxver !== -1 && protocolVersion > maxver)) {
|
|
106
|
+
throw new Error('Unknown protocol version: ' + protocolVersion + ' Expected: ' + minver + '-' + maxver)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return [reader, protocolVersion]
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
readDate (reader) {
|
|
114
|
+
const daysSinceZero = reader.uint(4)
|
|
115
|
+
const temp = new Date(0, 0, 1)
|
|
116
|
+
temp.setFullYear(0)
|
|
117
|
+
temp.setDate(daysSinceZero + 1)
|
|
118
|
+
return temp.toISOString().split('T')[0]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
decode (num, arr) {
|
|
122
|
+
if (num < 0 || num >= arr.length) {
|
|
123
|
+
return num
|
|
124
|
+
}
|
|
125
|
+
return arr[num]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class quake2 extends Core {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
this.encoding = 'latin1'
|
|
7
|
+
this.delimiter = '\n'
|
|
8
|
+
this.sendHeader = 'status'
|
|
9
|
+
this.responseHeader = 'print'
|
|
10
|
+
this.isQuake1 = false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async run (state) {
|
|
14
|
+
const body = await this.udpSend('\xff\xff\xff\xff' + this.sendHeader + '\x00', packet => {
|
|
15
|
+
const reader = this.reader(packet)
|
|
16
|
+
const header = reader.string({ length: 4, encoding: 'latin1' })
|
|
17
|
+
if (header !== '\xff\xff\xff\xff') return
|
|
18
|
+
let type
|
|
19
|
+
if (this.isQuake1) {
|
|
20
|
+
type = reader.string(this.responseHeader.length)
|
|
21
|
+
} else {
|
|
22
|
+
type = reader.string({ encoding: 'latin1' })
|
|
23
|
+
}
|
|
24
|
+
if (type !== this.responseHeader) return
|
|
25
|
+
return reader.rest()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const reader = this.reader(body)
|
|
29
|
+
const info = reader.string().split('\\')
|
|
30
|
+
if (info[0] === '') info.shift()
|
|
31
|
+
|
|
32
|
+
while (true) {
|
|
33
|
+
const key = info.shift()
|
|
34
|
+
const value = info.shift()
|
|
35
|
+
if (typeof value === 'undefined') break
|
|
36
|
+
state.raw[key] = value
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while (!reader.done()) {
|
|
40
|
+
const line = reader.string()
|
|
41
|
+
if (!line || line.charAt(0) === '\0') break
|
|
42
|
+
|
|
43
|
+
const args = []
|
|
44
|
+
const split = line.split('"')
|
|
45
|
+
split.forEach((part, i) => {
|
|
46
|
+
const inQuote = (i % 2 === 1)
|
|
47
|
+
if (inQuote) {
|
|
48
|
+
args.push(part)
|
|
49
|
+
} else {
|
|
50
|
+
const splitSpace = part.split(' ')
|
|
51
|
+
for (const subpart of splitSpace) {
|
|
52
|
+
if (subpart) args.push(subpart)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const player = {}
|
|
58
|
+
if (this.isQuake1) {
|
|
59
|
+
player.id = parseInt(args.shift())
|
|
60
|
+
player.score = parseInt(args.shift())
|
|
61
|
+
player.time = parseInt(args.shift())
|
|
62
|
+
player.ping = parseInt(args.shift())
|
|
63
|
+
player.name = args.shift()
|
|
64
|
+
player.skin = args.shift()
|
|
65
|
+
player.color1 = parseInt(args.shift())
|
|
66
|
+
player.color2 = parseInt(args.shift())
|
|
67
|
+
} else {
|
|
68
|
+
player.frags = parseInt(args.shift())
|
|
69
|
+
player.ping = parseInt(args.shift())
|
|
70
|
+
player.name = args.shift() || ''
|
|
71
|
+
if (!player.name) delete player.name
|
|
72
|
+
player.address = args.shift() || ''
|
|
73
|
+
if (!player.address) delete player.address
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
(player.ping ? state.players : state.bots).push(player)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ('g_needpass' in state.raw) state.password = state.raw.g_needpass
|
|
80
|
+
if ('mapname' in state.raw) state.map = state.raw.mapname
|
|
81
|
+
if ('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients
|
|
82
|
+
if ('maxclients' in state.raw) state.maxplayers = state.raw.maxclients
|
|
83
|
+
if ('sv_hostname' in state.raw) state.name = state.raw.sv_hostname
|
|
84
|
+
if ('hostname' in state.raw) state.name = state.raw.hostname
|
|
85
|
+
if ('clients' in state.raw) state.numplayers = state.raw.clients
|
|
86
|
+
else state.numplayers = state.players.length + state.bots.length
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import quake2 from './quake2.js'
|
|
2
|
+
|
|
3
|
+
export default class quake3 extends quake2 {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
this.sendHeader = 'getstatus'
|
|
7
|
+
this.responseHeader = 'statusResponse'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async run (state) {
|
|
11
|
+
await super.run(state)
|
|
12
|
+
state.name = this.stripColors(state.name)
|
|
13
|
+
for (const key of Object.keys(state.raw)) {
|
|
14
|
+
state.raw[key] = this.stripColors(state.raw[key])
|
|
15
|
+
}
|
|
16
|
+
for (const player of state.players) {
|
|
17
|
+
player.name = this.stripColors(player.name)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
stripColors (str) {
|
|
22
|
+
return str.replace(/\^(X.{6}|.)/g, '')
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class rfactor extends Core {
|
|
4
|
+
async run (state) {
|
|
5
|
+
const buffer = await this.udpSend('rF_S', b => b)
|
|
6
|
+
const reader = this.reader(buffer)
|
|
7
|
+
|
|
8
|
+
state.raw.gamename = this.readString(reader, 8)
|
|
9
|
+
state.raw.fullUpdate = reader.uint(1)
|
|
10
|
+
state.raw.region = reader.uint(2)
|
|
11
|
+
state.raw.ip = reader.part(4)
|
|
12
|
+
state.raw.size = reader.uint(2)
|
|
13
|
+
state.raw.version = reader.uint(2)
|
|
14
|
+
state.raw.versionRaceCast = reader.uint(2)
|
|
15
|
+
state.gamePort = reader.uint(2)
|
|
16
|
+
state.raw.queryPort = reader.uint(2)
|
|
17
|
+
state.raw.game = this.readString(reader, 20)
|
|
18
|
+
state.name = this.readString(reader, 28)
|
|
19
|
+
state.map = this.readString(reader, 32)
|
|
20
|
+
state.raw.motd = this.readString(reader, 96)
|
|
21
|
+
state.raw.packedAids = reader.uint(2)
|
|
22
|
+
state.raw.ping = reader.uint(2)
|
|
23
|
+
state.raw.packedFlags = reader.uint(1)
|
|
24
|
+
state.raw.rate = reader.uint(1)
|
|
25
|
+
state.numplayers = reader.uint(1)
|
|
26
|
+
state.maxplayers = reader.uint(1)
|
|
27
|
+
state.raw.bots = reader.uint(1)
|
|
28
|
+
state.raw.packedSpecial = reader.uint(1)
|
|
29
|
+
state.raw.damage = reader.uint(1)
|
|
30
|
+
state.raw.packedRules = reader.uint(2)
|
|
31
|
+
state.raw.credits1 = reader.uint(1)
|
|
32
|
+
state.raw.credits2 = reader.uint(2)
|
|
33
|
+
this.logger.debug(reader.offset())
|
|
34
|
+
state.raw.time = reader.uint(2)
|
|
35
|
+
state.raw.laps = reader.uint(2) / 16
|
|
36
|
+
reader.skip(3)
|
|
37
|
+
state.raw.vehicles = reader.string()
|
|
38
|
+
|
|
39
|
+
state.password = !!(state.raw.packedSpecial & 2)
|
|
40
|
+
state.raw.raceCast = !!(state.raw.packedSpecial & 4)
|
|
41
|
+
state.raw.fixedSetups = !!(state.raw.packedSpecial & 16)
|
|
42
|
+
|
|
43
|
+
const aids = [
|
|
44
|
+
'TractionControl',
|
|
45
|
+
'AntiLockBraking',
|
|
46
|
+
'StabilityControl',
|
|
47
|
+
'AutoShifting',
|
|
48
|
+
'AutoClutch',
|
|
49
|
+
'Invulnerability',
|
|
50
|
+
'OppositeLock',
|
|
51
|
+
'SteeringHelp',
|
|
52
|
+
'BrakingHelp',
|
|
53
|
+
'SpinRecovery',
|
|
54
|
+
'AutoPitstop'
|
|
55
|
+
]
|
|
56
|
+
state.raw.aids = []
|
|
57
|
+
for (let offset = 0; offset < aids.length; offset++) {
|
|
58
|
+
if (state.packedAids && (1 << offset)) {
|
|
59
|
+
state.raw.aids.push(aids[offset])
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Consumes bytesToConsume, but only returns string up to the first null
|
|
65
|
+
readString (reader, bytesToConsume) {
|
|
66
|
+
const consumed = reader.part(bytesToConsume)
|
|
67
|
+
return this.reader(consumed).string()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class samp extends Core {
|
|
4
|
+
constructor () {
|
|
5
|
+
super()
|
|
6
|
+
this.encoding = 'win1252'
|
|
7
|
+
this.magicHeader = 'SAMP'
|
|
8
|
+
this.responseMagicHeader = null
|
|
9
|
+
this.isVcmp = false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run (state) {
|
|
13
|
+
// read info
|
|
14
|
+
{
|
|
15
|
+
const reader = await this.sendPacket('i')
|
|
16
|
+
if (this.isVcmp) {
|
|
17
|
+
const consumed = reader.part(12)
|
|
18
|
+
state.raw.version = this.reader(consumed).string()
|
|
19
|
+
}
|
|
20
|
+
state.password = !!reader.uint(1)
|
|
21
|
+
state.numplayers = reader.uint(2)
|
|
22
|
+
state.maxplayers = reader.uint(2)
|
|
23
|
+
state.name = reader.pascalString(4)
|
|
24
|
+
state.raw.gamemode = reader.pascalString(4)
|
|
25
|
+
state.raw.map = reader.pascalString(4)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// read rules
|
|
29
|
+
if (!this.isVcmp) {
|
|
30
|
+
const reader = await this.sendPacket('r')
|
|
31
|
+
const ruleCount = reader.uint(2)
|
|
32
|
+
state.raw.rules = {}
|
|
33
|
+
for (let i = 0; i < ruleCount; i++) {
|
|
34
|
+
const key = reader.pascalString(1)
|
|
35
|
+
const value = reader.pascalString(1)
|
|
36
|
+
state.raw.rules[key] = value
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// read players
|
|
41
|
+
// don't even bother if > 100 players, because the server won't respond
|
|
42
|
+
if (state.numplayers < 100) {
|
|
43
|
+
if (this.isVcmp) {
|
|
44
|
+
const reader = await this.sendPacket('c', true)
|
|
45
|
+
if (reader !== null) {
|
|
46
|
+
const playerCount = reader.uint(2)
|
|
47
|
+
for (let i = 0; i < playerCount; i++) {
|
|
48
|
+
const player = {}
|
|
49
|
+
player.name = reader.pascalString(1)
|
|
50
|
+
state.players.push(player)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
const reader = await this.sendPacket('d', true)
|
|
55
|
+
if (reader !== null) {
|
|
56
|
+
const playerCount = reader.uint(2)
|
|
57
|
+
for (let i = 0; i < playerCount; i++) {
|
|
58
|
+
const player = {}
|
|
59
|
+
player.id = reader.uint(1)
|
|
60
|
+
player.name = reader.pascalString(1)
|
|
61
|
+
player.score = reader.int(4)
|
|
62
|
+
player.ping = reader.uint(4)
|
|
63
|
+
state.players.push(player)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async sendPacket (type, allowTimeout) {
|
|
71
|
+
const outBuffer = Buffer.alloc(11)
|
|
72
|
+
outBuffer.write(this.magicHeader, 0, 4)
|
|
73
|
+
const ipSplit = this.options.address.split('.')
|
|
74
|
+
outBuffer.writeUInt8(parseInt(ipSplit[0]), 4)
|
|
75
|
+
outBuffer.writeUInt8(parseInt(ipSplit[1]), 5)
|
|
76
|
+
outBuffer.writeUInt8(parseInt(ipSplit[2]), 6)
|
|
77
|
+
outBuffer.writeUInt8(parseInt(ipSplit[3]), 7)
|
|
78
|
+
outBuffer.writeUInt16LE(this.options.port, 8)
|
|
79
|
+
outBuffer.writeUInt8(type.charCodeAt(0), 10)
|
|
80
|
+
|
|
81
|
+
const checkBuffer = Buffer.from(outBuffer)
|
|
82
|
+
if (this.responseMagicHeader) {
|
|
83
|
+
checkBuffer.write(this.responseMagicHeader, 0, 4)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return await this.udpSend(
|
|
87
|
+
outBuffer,
|
|
88
|
+
(buffer) => {
|
|
89
|
+
const reader = this.reader(buffer)
|
|
90
|
+
for (let i = 0; i < checkBuffer.length; i++) {
|
|
91
|
+
if (checkBuffer.readUInt8(i) !== reader.uint(1)) return
|
|
92
|
+
}
|
|
93
|
+
return reader
|
|
94
|
+
},
|
|
95
|
+
() => {
|
|
96
|
+
if (allowTimeout) {
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class savage2 extends Core {
|
|
4
|
+
async run (state) {
|
|
5
|
+
const buffer = await this.udpSend('\x01', b => b)
|
|
6
|
+
const reader = this.reader(buffer)
|
|
7
|
+
|
|
8
|
+
reader.skip(12)
|
|
9
|
+
state.name = this.stripColorCodes(reader.string())
|
|
10
|
+
state.numplayers = reader.uint(1)
|
|
11
|
+
state.maxplayers = reader.uint(1)
|
|
12
|
+
state.raw.time = reader.string()
|
|
13
|
+
state.map = reader.string()
|
|
14
|
+
state.raw.nextmap = reader.string()
|
|
15
|
+
state.raw.location = reader.string()
|
|
16
|
+
state.raw.minplayers = reader.uint(1)
|
|
17
|
+
state.raw.gametype = reader.string()
|
|
18
|
+
state.raw.version = reader.string()
|
|
19
|
+
state.raw.minlevel = reader.uint(1)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
stripColorCodes (str) {
|
|
23
|
+
return str.replace(/\^./g, '')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Core from './core.js'
|
|
2
|
+
|
|
3
|
+
export default class starmade 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([0x00, 0x00, 0x00, 0x09, 0x2a, 0xff, 0xff, 0x01, 0x6f, 0x00, 0x00, 0x00, 0x00])
|
|
12
|
+
|
|
13
|
+
const payload = await this.withTcp(async socket => {
|
|
14
|
+
return await this.tcpSend(socket, b, buffer => {
|
|
15
|
+
if (buffer.length < 12) return
|
|
16
|
+
const reader = this.reader(buffer)
|
|
17
|
+
const packetLength = reader.uint(4)
|
|
18
|
+
this.logger.debug('Received packet length: ' + packetLength)
|
|
19
|
+
const timestamp = reader.uint(8).toString()
|
|
20
|
+
this.logger.debug('Received timestamp: ' + timestamp)
|
|
21
|
+
if (reader.remaining() < packetLength || reader.remaining() < 5) return
|
|
22
|
+
|
|
23
|
+
const checkId = reader.uint(1)
|
|
24
|
+
const packetId = reader.uint(2)
|
|
25
|
+
const commandId = reader.uint(1)
|
|
26
|
+
const type = reader.uint(1)
|
|
27
|
+
|
|
28
|
+
this.logger.debug('checkId=' + checkId + ' packetId=' + packetId + ' commandId=' + commandId + ' type=' + type)
|
|
29
|
+
if (checkId !== 0x2a) return
|
|
30
|
+
|
|
31
|
+
return reader.rest()
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const reader = this.reader(payload)
|
|
36
|
+
|
|
37
|
+
const data = []
|
|
38
|
+
state.raw.data = data
|
|
39
|
+
|
|
40
|
+
while (!reader.done()) {
|
|
41
|
+
const mark = reader.uint(1)
|
|
42
|
+
if (mark === 1) {
|
|
43
|
+
// signed int
|
|
44
|
+
data.push(reader.int(4))
|
|
45
|
+
} else if (mark === 3) {
|
|
46
|
+
// float
|
|
47
|
+
data.push(reader.float())
|
|
48
|
+
} else if (mark === 4) {
|
|
49
|
+
// string
|
|
50
|
+
data.push(reader.pascalString(2))
|
|
51
|
+
} else if (mark === 6) {
|
|
52
|
+
// byte
|
|
53
|
+
data.push(reader.uint(1))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.logger.debug('Received raw data array', data)
|
|
58
|
+
|
|
59
|
+
if (typeof data[0] === 'number') state.raw.infoVersion = data[0]
|
|
60
|
+
if (typeof data[1] === 'number') state.raw.version = data[1]
|
|
61
|
+
if (typeof data[2] === 'string') state.name = data[2]
|
|
62
|
+
if (typeof data[3] === 'string') state.raw.description = data[3]
|
|
63
|
+
if (typeof data[4] === 'number') state.raw.startTime = data[4]
|
|
64
|
+
if (typeof data[5] === 'number') state.numplayers = data[5]
|
|
65
|
+
if (typeof data[6] === 'number') state.maxplayers = data[6]
|
|
66
|
+
}
|
|
67
|
+
}
|