diff --git a/lib/games.js b/lib/games.js index bf6649d..ebc99b7 100644 --- a/lib/games.js +++ b/lib/games.js @@ -1586,6 +1586,14 @@ export const games = { protocol: 'valve' } }, + hytale: { + name: 'Hytale', + release_year: 2026, + options: { + port: 5523, + protocol: 'hytale' + } + }, i2cs: { name: 'IGI 2: Covert Strike', release_year: 2003, diff --git a/protocols/hytale.js b/protocols/hytale.js new file mode 100644 index 0000000..9c669f1 --- /dev/null +++ b/protocols/hytale.js @@ -0,0 +1,71 @@ +import Core from './core.js' + +export default class hytale extends Core { + async run (state) { + this.usedTcp = true + + // Hytale servers commonly use self-signed certificates for HTTPS. + // We default rejectUnauthorized to false unless explicitly set. + if (this.options.rejectUnauthorized === undefined) { + this.options.rejectUnauthorized = false + } + + let response + // Try HTTPS first (most common), fall back to HTTP + try { + response = await this.queryEndpoint('https') + } catch (e) { + this.logger.debug('HTTPS query failed, trying HTTP') + this.logger.debug(e) + response = await this.queryEndpoint('http') + } + + if (response.Server) { + state.name = response.Server.Name + state.version = response.Server.Version + state.maxplayers = response.Server.MaxPlayers + state.raw.server = response.Server + } + + if (response.Universe) { + state.numplayers = response.Universe.CurrentPlayers + state.map = response.Universe.DefaultWorld + state.raw.universe = response.Universe + } + + if (response.Players) { + state.players = response.Players.map(player => ({ + name: player.Name, + raw: { + uuid: player.UUID, + world: player.World + } + })) + } + + if (response.Plugins) { + state.raw.plugins = response.Plugins + } + } + + async queryEndpoint (protocol) { + const url = `${protocol}://${this.options.host}:${this.options.port}/Nitrado/Query` + + const requestOptions = { + url, + headers: { + Accept: 'application/json' + }, + responseType: 'json' + } + + if (protocol === 'https') { + requestOptions.https = { + minVersion: 'TLSv1.2', + rejectUnauthorized: this.options.rejectUnauthorized + } + } + + return await this.request(requestOptions) + } +} diff --git a/protocols/index.js b/protocols/index.js index 66efb97..be4fb31 100644 --- a/protocols/index.js +++ b/protocols/index.js @@ -25,6 +25,7 @@ import gtasao from './gtasao.js' import hawakening from './hawakening.js' import hawakeningmaster from './hawakeningmaster.js' import hexen2 from './hexen2.js' +import hytale from './hytale.js' import jc2mp from './jc2mp.js' import kspdmp from './kspdmp.js' import mafia2mp from './mafia2mp.js' @@ -78,7 +79,7 @@ import scpsl from './scpsl.js' export { armagetron, ase, asa, assettocorsa, battlefield, brokeprotocol, brokeprotocolmaster, buildandshoot, cs2d, discord, doom3, eco, epic, factorio, farmingsimulator, ffow, - fivem, gamespy1, gamespy2, gamespy3, geneshift, goldsrc, gtasao, hawakening, hawakeningmaster, hexen2, jc2mp, kspdmp, mafia2mp, mafia2online, minecraft, + fivem, gamespy1, gamespy2, gamespy3, geneshift, goldsrc, gtasao, hawakening, hawakeningmaster, hexen2, hytale, jc2mp, kspdmp, mafia2mp, mafia2online, minecraft, minecraftbedrock, minecraftvanilla, minetest, mumble, mumbleping, nadeo, openttd, palworld, quake1, quake2, quake3, renegadex, renegadexmaster, renown, rfactor, ragemp, samp, satisfactory, soldat, savage2, squad, starmade, starsiege, teamspeak2, teamspeak3, terraria, toxikk, tribes1, tribes1master, unreal2, ut3, valve, vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz, theisleevrima, xonotic, altvmp, vintagestorymaster, vintagestory, sdtd, scpsl