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,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,9 @@
1
+ import quake2 from './quake2.js'
2
+
3
+ export default class quake1 extends quake2 {
4
+ constructor () {
5
+ super()
6
+ this.responseHeader = 'n'
7
+ this.isQuake1 = true
8
+ }
9
+ }
@@ -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
+ }
@@ -0,0 +1,10 @@
1
+ import tribes1 from './tribes1.js'
2
+
3
+ export default class starsiege extends tribes1 {
4
+ constructor () {
5
+ super()
6
+ this.encoding = 'latin1'
7
+ this.requestByte = 0x72
8
+ this.responseByte = 0x73
9
+ }
10
+ }