feat(protocol/hytale): add support for Hytale servers (#750)

Add protocol implementation for querying Hytale game servers using
the Nitrado Query API endpoint. The protocol uses HTTPS with HTTP
fallback to fetch server information including name, version, player
count, and map.

Example:
  $ gamedig --type hytale play.hyfyve.net:5523

  {
    "name": "Hytale Server",
    "map": "default",
    "version": "2026.01.13-dcad8778f",
    "maxplayers": 100,
    "numplayers": 1,
    "connect": "play.hyfyve.net:5523"
  }

Co-authored-by: Thomas Koetsier <thomaskoetsier@Thomass-MacBook-Air.local>
This commit is contained in:
Thomas Koetsier 2026-02-02 22:57:27 +00:00 committed by GitHub
parent a9ae97a651
commit 1b2345fa02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 81 additions and 1 deletions

View file

@ -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,

71
protocols/hytale.js Normal file
View file

@ -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)
}
}

View file

@ -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