feat: add support for Palworld (#495)

* Added Palworld

* Tidy up

* Improve variable wording for Epic auth
This commit is contained in:
Jonathan Lambert 2024-01-20 22:36:05 +00:00 committed by GitHub
parent cbf66e127c
commit 96e2054a04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 81 additions and 5 deletions

View File

@ -203,6 +203,7 @@
| openarena | OpenArena | |
| openttd | OpenTTD | |
| painkiller | Painkiller | |
| palworld | Palworld | [EOS Protocol](#eos) |
| pce | Primal Carnage: Extinction | [Valve Protocol](#valve) |
| pixark | PixARK | [Valve Protocol](#valve) |
| postal2 | Postal 2 | |

View File

@ -1754,6 +1754,14 @@ export const games = {
},
release_year: 2004
},
palworld: {
name: 'Palworld',
release_year: 2024,
options: {
port: 8221,
protocol: 'palworld'
}
},
pvak2: {
name: 'Pirates, Vikings, and Knights II',
options: {

View File

@ -15,6 +15,9 @@ export default class Epic extends Core {
this.clientSecret = null
this.deploymentId = null
this.epicApi = 'https://api.epicgames.dev'
this.authByExternalToken = false // Some games require a client access token to POST to the matchmaking endpoint.
this.deviceIdAccessToken = null
this.accessToken = null
// Don't use the tcp ping probing
@ -22,13 +25,18 @@ export default class Epic extends Core {
}
async run (state) {
await this.getAccessToken()
if (this.authByExternalToken) {
await this.getExternalAccessToken()
} else {
await this.getClientAccessToken()
}
await this.queryInfo(state)
await this.cleanup(state)
}
async getAccessToken () {
this.logger.debug('Requesting acess token ...')
async getClientAccessToken () {
this.logger.debug('Requesting client access token ...')
const url = `${this.epicApi}/auth/v1/oauth/token`
const body = `grant_type=client_credentials&deployment_id=${this.deploymentId}`
@ -43,6 +51,50 @@ export default class Epic extends Core {
this.accessToken = response.access_token
}
async _getDeviceIdToken () {
this.logger.debug('Requesting deviceId access token ...')
const url = `${this.epicApi}/auth/v1/accounts/deviceid`
const body = 'deviceModel=PC'
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 this.request({ url, body, headers, method: 'POST', responseType: 'json' })
return response.access_token
}
async getExternalAccessToken () {
this.logger.debug('Requesting external access token ...')
const deviceIdToken = await this._getDeviceIdToken()
const url = `${this.epicApi}/auth/v1/oauth/token`
const bodyParts = [
'grant_type=external_auth',
'external_auth_type=deviceid_access_token',
`external_auth_token=${deviceIdToken}`,
'nonce=ABCHFA3qgUCJ1XTPAoGDEF', // This is required but can be set to anything
`deployment_id=${this.deploymentId}`,
'display_name=User'
]
const body = bodyParts.join('&')
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 this.request({ url, body, headers, method: 'POST', responseType: 'json' })
this.accessToken = response.access_token
}
async queryInfo (state) {
const url = `${this.epicApi}/matchmaking/v1/${this.deploymentId}/filter`
const body = {
@ -65,7 +117,8 @@ export default class Epic extends Core {
// 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}`
session.attributes.ADDRESSBOUND_s === `${this.options.address}:${this.options.port}` ||
session.attributes.GAMESERVER_PORT_l === this.options.port
const desiredServer = response.sessions.find(hasDesiredPort)

View File

@ -29,6 +29,7 @@ import mumble from './mumble.js'
import mumbleping from './mumbleping.js'
import nadeo from './nadeo.js'
import openttd from './openttd.js'
import palworld from './palworld.js'
import quake1 from './quake1.js'
import quake2 from './quake2.js'
import quake3 from './quake3.js'
@ -55,7 +56,7 @@ import dayz from './dayz.js'
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,
minecraftbedrock, minecraftvanilla, mumble, mumbleping, nadeo, openttd, palworld, quake1, quake2, quake3, rfactor, samp,
savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, tribes1, tribes1master, unreal2, ut3, valve,
vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz
}

13
protocols/palworld.js Normal file
View File

@ -0,0 +1,13 @@
import Epic from './epic.js'
export default class palworld extends Epic {
constructor () {
super()
// OAuth2 credentials extracted from Palworld files.
this.clientId = 'xyza78916PZ5DF0fAahu4tnrKKyFpqRE'
this.clientSecret = 'j0NapLEPm3R3EOrlQiM8cRLKq3Rt02ZVVwT0SkZstSg'
this.deploymentId = '0a18471f93d448e2a1f60e47e03d3413'
this.authByExternalToken = true
}
}