From 96e2054a0461d3118b650a2b7bd806574f1d49ab Mon Sep 17 00:00:00 2001 From: Jonathan Lambert Date: Sat, 20 Jan 2024 22:36:05 +0000 Subject: [PATCH] feat: add support for Palworld (#495) * Added Palworld * Tidy up * Improve variable wording for Epic auth --- GAMES_LIST.md | 1 + lib/games.js | 8 ++++++ protocols/epic.js | 61 ++++++++++++++++++++++++++++++++++++++++--- protocols/index.js | 3 ++- protocols/palworld.js | 13 +++++++++ 5 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 protocols/palworld.js diff --git a/GAMES_LIST.md b/GAMES_LIST.md index f990d13..0fc2237 100644 --- a/GAMES_LIST.md +++ b/GAMES_LIST.md @@ -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 | | diff --git a/lib/games.js b/lib/games.js index 05ea501..ad0354e 100644 --- a/lib/games.js +++ b/lib/games.js @@ -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: { diff --git a/protocols/epic.js b/protocols/epic.js index 5225fcf..5fc5960 100644 --- a/protocols/epic.js +++ b/protocols/epic.js @@ -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) diff --git a/protocols/index.js b/protocols/index.js index ab72a60..89571cf 100644 --- a/protocols/index.js +++ b/protocols/index.js @@ -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 } diff --git a/protocols/palworld.js b/protocols/palworld.js new file mode 100644 index 0000000..6894bb2 --- /dev/null +++ b/protocols/palworld.js @@ -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 + } +}