feat: Add support for ARK: Survival Ascended using EOS protocol (#402)

* Add epic protocol

* Add support for ARK: Survival Ascended

- Using epic protocol

* Fix online player count

* Final cleanup
This commit is contained in:
Guilherme Werner 2023-11-12 06:38:59 -03:00 committed by GitHub
parent c49d463858
commit a8bc7521f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 280 additions and 3 deletions

View File

@ -23,6 +23,7 @@
* Added Deno support: the library and CLI can now be experimentally used with the [Deno runtime](https://deno.com)
* `deno run --allow-net --allow-read=. bin/gamedig.js --type tf2 127.0.0.1`
* Added code examples.
* Added Epic Online Services protocol.
#### Games
* Added support by @dgibbs64: Eco (2018), Core Keeper (2022), ARMA: Reforger (2022),
@ -39,6 +40,7 @@ San Andreas OpenMP.
a placeholder in the `players` field.
* Fixed wrong field being parsed for `maxplayers` on Doom3.
* Stabilized field `numplayers`.
* Added support by @GuilhermeWerner: ARK: Survival Ascended (2023).
### 4.1.0
* Replace `compressjs` dependency by `seek-bzip` to solve some possible import issues.

View File

@ -8,6 +8,7 @@ aoe2|Age of Empires 2 (1999)|ase|port_query=27224
alienarena|Alien Arena (2004)|quake2|port_query=27910
alienswarm|Alien Swarm (2010)|valve|port=27015
arkse|Ark: Survival Evolved (2017)|valve|port=7777,port_query=27015
asa|Ark: Survival Ascended (2023)|asa|port=7777
assettocorsa|Assetto Corsa (2014)|assettocorsa|port=9610
atlas|Atlas (2018)|valve|port=5761,port_query_offset=51800
avorion|Avorion (2020)|valve|port=27000,port_query_offset=20

152
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "4.1.0",
"license": "MIT",
"dependencies": {
"axios": "^1.6.1",
"cheerio": "^1.0.0-rc.12",
"gbxremote": "^0.2.1",
"got": "^13.0.0",
@ -396,6 +397,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@ -408,6 +414,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
"integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -578,6 +594,17 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -716,6 +743,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -1366,6 +1401,25 @@
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@ -1375,6 +1429,19 @@
"is-callable": "^1.1.3"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data-encoder": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
@ -2118,6 +2185,25 @@
"node": ">=10"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-response": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
@ -2411,6 +2497,11 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
@ -3353,12 +3444,27 @@
"is-shared-array-buffer": "^1.0.2"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
"dev": true
},
"axios": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
"integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -3495,6 +3601,14 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -3591,6 +3705,11 @@
"object-keys": "^1.1.1"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -4064,6 +4183,11 @@
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"dev": true
},
"follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q=="
},
"for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@ -4073,6 +4197,16 @@
"is-callable": "^1.1.3"
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"form-data-encoder": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
@ -4596,6 +4730,19 @@
"yallist": "^4.0.0"
}
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"mimic-response": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
@ -4805,6 +4952,11 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",

View File

@ -52,6 +52,7 @@
"README.md"
],
"dependencies": {
"axios": "^1.6.1",
"cheerio": "^1.0.0-rc.12",
"gbxremote": "^0.2.1",
"got": "^13.0.0",

12
protocols/asa.js Normal file
View File

@ -0,0 +1,12 @@
import Epic from './epic.js'
export default class asa extends Epic {
constructor () {
super()
// OAuth2 credentials extracted from ARK: Survival Ascended files.
this.clientId = 'xyza7891muomRmynIIHaJB9COBKkwj6n'
this.clientSecret = 'PP5UGxysEieNfSrEicaD1N2Bb3TdXuD7xHYcsdUHZ7s'
this.deploymentId = 'ad9a8feffb3b4b2ca315546f038c3ae2'
}
}

107
protocols/epic.js Normal file
View File

@ -0,0 +1,107 @@
import Core from './core.js'
import axios from 'axios'
export default class Epic extends Core {
constructor () {
super()
/**
* To get information about game servers using Epic's EOS, you need some credentials to authenticate using OAuth2.
*
* https://dev.epicgames.com/docs/web-api-ref/authentication
*
* These credentials can be provided by the game developers or extracted from the game's files.
*/
this.clientId = null
this.clientSecret = null
this.deploymentId = null
this.epicApi = 'https://api.epicgames.dev'
this.accessToken = null
}
async run (state) {
await this.getAccessToken()
await this.queryInfo(state)
await this.cleanup(state)
}
async getAccessToken () {
this.logger.debug('Requesting acess token ...')
const url = `${this.epicApi}/auth/v1/oauth/token`
const body = `grant_type=client_credentials&deployment_id=${this.deploymentId}`
const headers = {
Authorization: `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
this.logger.debug(`POST: ${url}`)
const response = await axios.post(url, body, { headers })
if (response.status !== 200) {
throw new Error('Failed to get OAuth token')
}
this.accessToken = response.data.access_token
}
async queryInfo (state) {
const url = `${this.epicApi}/matchmaking/v1/${this.deploymentId}/filter`
const body = {
criteria: [
{
key: 'attributes.ADDRESS_s',
op: 'EQUAL',
value: this.options.address
}
]
}
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${this.accessToken}`
}
this.logger.debug(`POST: ${url}`)
const response = await axios.post(url, body, { headers })
if (response.status !== 200) {
throw new Error('Failed to get server info')
}
const reader = response.data
// Epic returns a list of sessions, we need to find the one with the desired port.
const hasDesiredPort = (session) => session.attributes.ADDRESSBOUND_s === `0.0.0.0:${this.options.port}` ||
session.attributes.ADDRESSBOUND_s === `${this.options.address}:${this.options.port}`
const desiredServer = reader.sessions.find(hasDesiredPort)
if (!desiredServer) {
throw new Error('Server not found')
}
state.name = desiredServer.attributes.CUSTOMSERVERNAME_s
state.map = desiredServer.attributes.MAPNAME_s
state.password = desiredServer.attributes.SERVERPASSWORD_b
state.maxplayers = desiredServer.settings.maxPublicPlayers
// If the game returns the player list, we can use it otherwise we use the total players.
if (desiredServer.totalPlayers === desiredServer.publicPlayers.length) {
for (const player of desiredServer.publicPlayers) {
state.players.push({
name: player.name,
raw: player
})
}
} else {
for (let i = 0; i < desiredServer.totalPlayers; i++) {
state.players.push('')
}
}
state.raw = desiredServer
}
async cleanup (state) {
this.accessToken = null
}
}

View File

@ -1,5 +1,6 @@
import armagetron from './armagetron.js'
import ase from './ase.js'
import asa from './asa.js'
import assettocorsa from './assettocorsa.js'
import battlefield from './battlefield.js'
import buildandshoot from './buildandshoot.js'
@ -7,6 +8,7 @@ import cs2d from './cs2d.js'
import discord from './discord.js'
import doom3 from './doom3.js'
import eco from './eco.js'
import epic from './epic.js'
import ffow from './ffow.js'
import fivem from './fivem.js'
import gamespy1 from './gamespy1.js'
@ -46,10 +48,10 @@ import vcmp from './vcmp.js'
import ventrilo from './ventrilo.js'
import warsow from './warsow.js'
export {
armagetron, ase, assettocorsa, battlefield, buildandshoot, cs2d, discord, doom3, eco, ffow, fivem, gamespy1,
export {
armagetron, ase, asa, assettocorsa, battlefield, buildandshoot, cs2d, discord, doom3, eco, epic, ffow, fivem, gamespy1,
gamespy2, gamespy3, geneshift, goldsrc, hexen2, jc2mp, kspdmp, mafia2mp, mafia2online, minecraft,
minecraftbedrock, minecraftvanilla, mumble, mumbleping, nadeo, openttd, quake1, quake2, quake3, rfactor, samp,
savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, tribes1, tribes1master, unreal2, ut3, valve,
vcmp, ventrilo, warsow
vcmp, ventrilo, warsow
}