@hytaleone/query 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +17 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -155,7 +155,7 @@ function validateResponse(buf) {
|
|
|
155
155
|
return buf.subarray(0, RESPONSE_MAGIC.length).equals(RESPONSE_MAGIC);
|
|
156
156
|
}
|
|
157
157
|
function parseCapabilities(reader) {
|
|
158
|
-
if (reader.remaining
|
|
158
|
+
if (reader.remaining === 3) {
|
|
159
159
|
const caps = reader.readUInt16LE();
|
|
160
160
|
const v2Version = reader.readUInt8();
|
|
161
161
|
return {
|
|
@@ -166,21 +166,27 @@ function parseCapabilities(reader) {
|
|
|
166
166
|
}
|
|
167
167
|
return { supportsV2: false, isNetworkMode: false, v2Version: 0 };
|
|
168
168
|
}
|
|
169
|
+
function isLegacyPortFormat(buf, offsetAfterMaxPlayers) {
|
|
170
|
+
const versionLenOffset = offsetAfterMaxPlayers + 2;
|
|
171
|
+
if (versionLenOffset + 2 > buf.length) return true;
|
|
172
|
+
const versionLen = buf.readUInt16LE(versionLenOffset);
|
|
173
|
+
return versionLen === 0 || versionLen > 500;
|
|
174
|
+
}
|
|
169
175
|
function parseBasicResponse(buf) {
|
|
170
176
|
if (!validateResponse(buf)) {
|
|
171
177
|
throw new Error("Invalid response: magic mismatch");
|
|
172
178
|
}
|
|
173
179
|
const reader = new BufferReader(buf);
|
|
174
|
-
reader.readBytes(RESPONSE_MAGIC.length);
|
|
175
|
-
reader.readBytes(1);
|
|
180
|
+
reader.readBytes(RESPONSE_MAGIC.length + 1);
|
|
176
181
|
const serverName = reader.readString();
|
|
177
182
|
const motd = reader.readString();
|
|
178
183
|
const currentPlayers = reader.readInt32LE();
|
|
179
184
|
const maxPlayers = reader.readInt32LE();
|
|
180
|
-
const
|
|
185
|
+
const legacy = isLegacyPortFormat(buf, reader.position);
|
|
186
|
+
const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();
|
|
181
187
|
const version = reader.readString();
|
|
182
|
-
const protocolVersion = reader.readInt32LE();
|
|
183
|
-
const protocolHash = reader.readString();
|
|
188
|
+
const protocolVersion = legacy ? 0 : reader.readInt32LE();
|
|
189
|
+
const protocolHash = legacy ? "" : reader.readString();
|
|
184
190
|
const capabilities = parseCapabilities(reader);
|
|
185
191
|
return {
|
|
186
192
|
serverName,
|
|
@@ -199,16 +205,16 @@ function parseFullResponse(buf) {
|
|
|
199
205
|
throw new Error("Invalid response: magic mismatch");
|
|
200
206
|
}
|
|
201
207
|
const reader = new BufferReader(buf);
|
|
202
|
-
reader.readBytes(RESPONSE_MAGIC.length);
|
|
203
|
-
reader.readBytes(1);
|
|
208
|
+
reader.readBytes(RESPONSE_MAGIC.length + 1);
|
|
204
209
|
const serverName = reader.readString();
|
|
205
210
|
const motd = reader.readString();
|
|
206
211
|
const currentPlayers = reader.readInt32LE();
|
|
207
212
|
const maxPlayers = reader.readInt32LE();
|
|
208
|
-
const
|
|
213
|
+
const legacy = isLegacyPortFormat(buf, reader.position);
|
|
214
|
+
const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();
|
|
209
215
|
const version = reader.readString();
|
|
210
|
-
const protocolVersion = reader.readInt32LE();
|
|
211
|
-
const protocolHash = reader.readString();
|
|
216
|
+
const protocolVersion = legacy ? 0 : reader.readInt32LE();
|
|
217
|
+
const protocolHash = legacy ? "" : reader.readString();
|
|
212
218
|
const playerCount = reader.readInt32LE();
|
|
213
219
|
const players = [];
|
|
214
220
|
for (let i = 0; i < playerCount; i++) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/query.ts","../src/protocol.ts","../src/query-v2.ts","../src/types-v2.ts","../src/protocol-v2.ts"],"sourcesContent":["export { query } from './query.js';\nexport { queryV2, clearChallengeCache } from './query-v2.js';\nexport type {\n ServerInfo,\n ServerInfoFull,\n Player,\n Plugin,\n QueryOptions,\n} from './types.js';\nexport type {\n ServerInfoV2,\n ServerInfoV2WithPlayers,\n QueryV2Options,\n} from './types-v2.js';\nexport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\n","import dgram from 'node:dgram';\nimport {\n buildRequest,\n parseBasicResponse,\n parseFullResponse,\n TYPE_BASIC,\n TYPE_FULL,\n} from './protocol.js';\nimport type { QueryOptions, ServerInfo, ServerInfoFull } from './types.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n/**\n * Send a UDP query and wait for response.\n */\nfunction sendQuery(\n host: string,\n port: number,\n type: number,\n timeout: number\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n const request = buildRequest(type);\n socket.send(request, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Query a Hytale server for information.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (full if options.full is true)\n *\n * @example\n * ```typescript\n * // Basic query\n * const info = await query('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // Full query (includes players + plugins)\n * const full = await query('play.example.com', 5520, { full: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function query(\n host: string,\n port?: number,\n options?: QueryOptions & { full?: false }\n): Promise<ServerInfo>;\nexport async function query(\n host: string,\n port: number,\n options: QueryOptions & { full: true }\n): Promise<ServerInfoFull>;\nexport async function query(\n host: string,\n port = 5520,\n options: QueryOptions & { full?: boolean } = {}\n): Promise<ServerInfo | ServerInfoFull> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const type = options.full ? TYPE_FULL : TYPE_BASIC;\n const response = await sendQuery(host, port, type, timeout);\n\n if (options.full) {\n return parseFullResponse(response);\n }\n return parseBasicResponse(response);\n}\n","import type { Player, Plugin, ServerInfo, ServerInfoFull } from './types.js';\n\n// Protocol constants\nexport const REQUEST_MAGIC = Buffer.from('HYQUERY\\0', 'ascii');\nexport const RESPONSE_MAGIC = Buffer.from('HYREPLY\\0', 'ascii');\nexport const TYPE_BASIC = 0x00;\nexport const TYPE_FULL = 0x01;\n\n// Capability flags (V1 extension)\nexport const CAP_V2_PROTOCOL = 0x01;\nexport const CAP_NETWORK_MODE = 0x02;\n\n/**\n * Build a query request packet.\n */\nexport function buildRequest(type: number): Buffer {\n const buf = Buffer.alloc(REQUEST_MAGIC.length + 1);\n REQUEST_MAGIC.copy(buf, 0);\n buf[REQUEST_MAGIC.length] = type;\n return buf;\n}\n\n/**\n * Buffer reader helper for parsing responses.\n */\nexport class BufferReader {\n private offset = 0;\n\n constructor(private buf: Buffer) {}\n\n private checkBounds(needed: number): void {\n if (this.offset + needed > this.buf.length) {\n throw new RangeError(\n `Buffer underflow: need ${needed} bytes at offset ${this.offset}, but buffer is ${this.buf.length} bytes`\n );\n }\n }\n\n readBytes(length: number): Buffer {\n this.checkBounds(length);\n const slice = this.buf.subarray(this.offset, this.offset + length);\n this.offset += length;\n return slice;\n }\n\n readUInt8(): number {\n this.checkBounds(1);\n return this.buf[this.offset++];\n }\n\n readUInt16LE(): number {\n this.checkBounds(2);\n const value = this.buf.readUInt16LE(this.offset);\n this.offset += 2;\n return value;\n }\n\n readUInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readUInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readBigInt64BE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64BE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readBoolean(): boolean {\n this.checkBounds(1);\n return this.buf[this.offset++] !== 0;\n }\n\n readString(): string {\n const length = this.readUInt16LE();\n if (length > this.remaining) {\n throw new RangeError(\n `Invalid string length ${length} at offset ${this.offset - 2}, only ${this.remaining} bytes remaining`\n );\n }\n const bytes = this.readBytes(length);\n return bytes.toString('utf8');\n }\n\n readUUID(): string {\n const msb = this.readBigInt64BE();\n const lsb = this.readBigInt64BE();\n return formatUUID(msb, lsb);\n }\n\n readBigInt64LE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64LE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readUUIDLE(): string {\n const msb = this.readBigInt64LE();\n const lsb = this.readBigInt64LE();\n return formatUUID(msb, lsb);\n }\n\n get remaining(): number {\n return this.buf.length - this.offset;\n }\n\n get position(): number {\n return this.offset;\n }\n}\n\n/**\n * Format UUID from most/least significant bits.\n */\nfunction formatUUID(msb: bigint, lsb: bigint): string {\n const toHex = (n: bigint): string => {\n if (n < 0n) {\n n = BigInt.asUintN(64, n);\n }\n return n.toString(16).padStart(16, '0');\n };\n\n const hex = toHex(msb) + toHex(lsb);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Validate response magic bytes.\n */\nexport function validateResponse(buf: Buffer): boolean {\n if (buf.length < RESPONSE_MAGIC.length + 1) {\n return false;\n }\n return buf.subarray(0, RESPONSE_MAGIC.length).equals(RESPONSE_MAGIC);\n}\n\n/**\n * Parse capability extension from remaining bytes (3 bytes: 2 caps + 1 version).\n */\nfunction parseCapabilities(reader: BufferReader): {\n supportsV2: boolean;\n isNetworkMode: boolean;\n v2Version: number;\n} {\n if (reader.remaining >= 3) {\n const caps = reader.readUInt16LE();\n const v2Version = reader.readUInt8();\n return {\n supportsV2: (caps & CAP_V2_PROTOCOL) !== 0,\n isNetworkMode: (caps & CAP_NETWORK_MODE) !== 0,\n v2Version,\n };\n }\n return { supportsV2: false, isNetworkMode: false, v2Version: 0 };\n}\n\n/**\n * Parse a basic query response.\n */\nexport function parseBasicResponse(buf: Buffer): ServerInfo {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(RESPONSE_MAGIC.length);\n\n // Skip type\n reader.readBytes(1);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const hostPort = reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n ...capabilities,\n };\n}\n\n/**\n * Parse a full query response.\n */\nexport function parseFullResponse(buf: Buffer): ServerInfoFull {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(RESPONSE_MAGIC.length);\n\n // Skip type\n reader.readBytes(1);\n\n // Base info\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const hostPort = reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n // Player list\n const playerCount = reader.readInt32LE();\n const players: Player[] = [];\n for (let i = 0; i < playerCount; i++) {\n players.push({\n name: reader.readString(),\n uuid: reader.readUUID(),\n });\n }\n\n // Plugin list\n const pluginCount = reader.readInt32LE();\n const plugins: Plugin[] = [];\n for (let i = 0; i < pluginCount; i++) {\n plugins.push({\n id: reader.readString(),\n version: reader.readString(),\n enabled: reader.readBoolean(),\n });\n }\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n players,\n plugins,\n ...capabilities,\n };\n}\n","import dgram from 'node:dgram';\nimport type { Player } from './types.js';\nimport type { QueryV2Options, ServerInfoV2, ServerInfoV2WithPlayers } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport {\n buildChallengeRequest,\n buildV2Request,\n findTLVEntry,\n parseChallengeResponse,\n parseTLVPlayerList,\n parseTLVServerInfo,\n parseV2Response,\n} from './protocol-v2.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n// Challenge token cache configuration\nconst TOKEN_TTL_MS = 30_000;\nconst TOKEN_REFRESH_BUFFER_MS = 5_000;\nconst CLEANUP_INTERVAL_MS = 60_000;\n\ninterface CachedChallenge {\n token: Buffer;\n expiresAt: number;\n requestId: number;\n}\n\n// Module-level cache\nconst challengeCache = new Map<string, CachedChallenge>();\nlet cleanupScheduled = false;\n\nfunction scheduleCleanup(): void {\n if (cleanupScheduled) return;\n cleanupScheduled = true;\n\n setTimeout(() => {\n const now = Date.now();\n for (const [key, entry] of challengeCache) {\n if (now >= entry.expiresAt) {\n challengeCache.delete(key);\n }\n }\n cleanupScheduled = false;\n if (challengeCache.size > 0) {\n scheduleCleanup();\n }\n }, CLEANUP_INTERVAL_MS).unref();\n}\n\n/**\n * Clear the challenge token cache.\n * Useful for testing or forcing fresh challenges.\n */\nexport function clearChallengeCache(): void {\n challengeCache.clear();\n}\n\n/**\n * Send a UDP packet and wait for response.\n */\nfunction sendUDP(host: string, port: number, data: Buffer, timeout: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n socket.send(data, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Fetch a new challenge token from the server.\n */\nasync function fetchChallenge(host: string, port: number, timeout: number): Promise<Buffer> {\n const request = buildChallengeRequest();\n const response = await sendUDP(host, port, request, timeout);\n return parseChallengeResponse(response);\n}\n\n/**\n * Get or refresh a challenge token for a host:port.\n */\nasync function ensureChallenge(\n host: string,\n port: number,\n timeout: number\n): Promise<CachedChallenge> {\n const key = `${host}:${port}`;\n const cached = challengeCache.get(key);\n const now = Date.now();\n\n // Return cached if still valid (with buffer time)\n if (cached && now < cached.expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n cached.requestId++;\n return cached;\n }\n\n // Fetch new challenge token\n const token = await fetchChallenge(host, port, timeout);\n const entry: CachedChallenge = {\n token,\n expiresAt: now + TOKEN_TTL_MS,\n requestId: 1,\n };\n\n challengeCache.set(key, entry);\n scheduleCleanup();\n\n return entry;\n}\n\n/**\n * Perform a V2 query request.\n */\nasync function performV2Query(\n host: string,\n port: number,\n type: number,\n offset: number,\n timeout: number,\n authToken?: string\n): Promise<{ header: ReturnType<typeof parseV2Response>['header']; entries: ReturnType<typeof parseV2Response>['entries'] }> {\n const challenge = await ensureChallenge(host, port, timeout);\n\n const request = buildV2Request(type, challenge.token, challenge.requestId, 0, offset, authToken);\n const response = await sendUDP(host, port, request, timeout);\n\n return parseV2Response(response);\n}\n\n/**\n * Query a Hytale server using V2 protocol (basic info only).\n */\nexport async function queryV2(\n host: string,\n port?: number,\n options?: QueryV2Options & { players?: false }\n): Promise<ServerInfoV2>;\n\n/**\n * Query a Hytale server using V2 protocol with player list.\n */\nexport async function queryV2(\n host: string,\n port: number,\n options: QueryV2Options & { players: true | 'all' }\n): Promise<ServerInfoV2WithPlayers>;\n\n/**\n * Query a Hytale server using V2 protocol.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (with players if requested)\n *\n * @example\n * ```typescript\n * // Basic V2 query\n * const info = await queryV2('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // V2 query with players\n * const full = await queryV2('play.example.com', 5520, { players: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n *\n * // V2 query with all players (auto-pagination)\n * const all = await queryV2('play.example.com', 5520, { players: 'all' });\n * console.log(`All ${all.totalPlayers} players:`, all.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function queryV2(\n host: string,\n port = 5520,\n options: QueryV2Options = {}\n): Promise<ServerInfoV2 | ServerInfoV2WithPlayers> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const wantPlayers = options.players === true || options.players === 'all';\n const offset = options.playerOffset ?? 0;\n\n // First, always do a BASIC query to get server info\n const { header: basicHeader, entries: basicEntries } = await performV2Query(\n host,\n port,\n V2QueryType.BASIC,\n 0,\n timeout,\n options.authToken\n );\n\n const hasAddress = (basicHeader.flags & V2ResponseFlag.HAS_ADDRESS) !== 0;\n const isNetwork = (basicHeader.flags & V2ResponseFlag.IS_NETWORK) !== 0;\n\n // Parse server info\n const serverInfoEntry = findTLVEntry(basicEntries, V2TLVType.SERVER_INFO);\n if (!serverInfoEntry) {\n throw new Error('Response missing SERVER_INFO TLV');\n }\n const serverInfo = parseTLVServerInfo(serverInfoEntry, hasAddress, isNetwork);\n\n // If players not requested, return basic info\n if (!wantPlayers) {\n return serverInfo;\n }\n\n // Do a PLAYERS query to get player list\n const { header: playersHeader, entries: playersEntries } = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n offset,\n timeout,\n options.authToken\n );\n\n const hasMorePlayers = (playersHeader.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n\n // Parse player list\n const playerListEntry = findTLVEntry(playersEntries, V2TLVType.PLAYER_LIST);\n if (!playerListEntry) {\n throw new Error('Response missing PLAYER_LIST TLV');\n }\n const playerList = parseTLVPlayerList(playerListEntry);\n\n let players = playerList.players;\n let currentOffset = playerList.offset + players.length;\n let hasMore = hasMorePlayers;\n\n // If 'all' mode, paginate through remaining players\n if (options.players === 'all' && hasMore) {\n const allPlayers: Player[] = [...players];\n\n while (hasMore) {\n const nextResult = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n currentOffset,\n timeout,\n options.authToken\n );\n\n const nextHasMore = (nextResult.header.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n const nextPlayerEntry = findTLVEntry(nextResult.entries, V2TLVType.PLAYER_LIST);\n\n if (!nextPlayerEntry) {\n break;\n }\n\n const nextPlayerList = parseTLVPlayerList(nextPlayerEntry);\n allPlayers.push(...nextPlayerList.players);\n currentOffset = nextPlayerList.offset + nextPlayerList.players.length;\n hasMore = nextHasMore;\n }\n\n players = allPlayers;\n hasMore = false;\n }\n\n return {\n ...serverInfo,\n players,\n totalPlayers: playerList.totalPlayers,\n offset: playerList.offset,\n hasMore,\n };\n}\n","import type { Player } from './types.js';\n\n/**\n * V2 query type constants.\n */\nexport const V2QueryType = {\n CHALLENGE: 0x00,\n BASIC: 0x01,\n PLAYERS: 0x02,\n} as const;\n\n/**\n * V2 response flag constants.\n */\nexport const V2ResponseFlag = {\n HAS_MORE_PLAYERS: 0x0001,\n AUTH_REQUIRED: 0x0002,\n IS_NETWORK: 0x0010,\n HAS_ADDRESS: 0x0020,\n} as const;\n\n/**\n * V2 TLV type constants.\n */\nexport const V2TLVType = {\n SERVER_INFO: 0x0001,\n PLAYER_LIST: 0x0002,\n} as const;\n\n/**\n * Basic server information from V2 protocol.\n */\nexport interface ServerInfoV2 {\n /** Server display name */\n serverName: string;\n /** Message of the day */\n motd: string;\n /** Current number of players online */\n currentPlayers: number;\n /** Maximum player capacity */\n maxPlayers: number;\n /** Server version string */\n version: string;\n /** Protocol version number */\n protocolVersion: number;\n /** Protocol hash string */\n protocolHash: string;\n /** Server host (if FLAG_RESPONSE_HAS_ADDRESS) */\n host?: string;\n /** Server port (if FLAG_RESPONSE_HAS_ADDRESS) */\n hostPort?: number;\n /** Whether the server is in network aggregation mode */\n isNetwork: boolean;\n}\n\n/**\n * V2 server info combined with player list.\n */\nexport interface ServerInfoV2WithPlayers extends ServerInfoV2 {\n /** List of online players */\n players: Player[];\n /** Total players on server */\n totalPlayers: number;\n /** Offset used for this response */\n offset: number;\n /** Whether more players are available */\n hasMore: boolean;\n}\n\n/**\n * Options for V2 query function.\n */\nexport interface QueryV2Options {\n /** Timeout in milliseconds (default: 5000) */\n timeout?: number;\n /** Request player list: false=basic only, true=single page, 'all'=paginate all */\n players?: boolean | 'all';\n /** Starting offset for player pagination */\n playerOffset?: number;\n /** Optional auth token for private servers */\n authToken?: string;\n}\n","import type { Player } from './types.js';\nimport type { ServerInfoV2 } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport { BufferReader } from './protocol.js';\n\n// Magic bytes for V2 protocol\nexport const V2_REQUEST_MAGIC = Buffer.from('ONEQUERY', 'ascii');\nexport const V2_RESPONSE_MAGIC = Buffer.from('ONEREPLY', 'ascii');\n\n// Challenge token size\nexport const CHALLENGE_TOKEN_SIZE = 32;\n\n/**\n * Build a challenge request packet.\n * Format: Magic (8) + Type (1)\n */\nexport function buildChallengeRequest(): Buffer {\n const buf = Buffer.alloc(V2_REQUEST_MAGIC.length + 1);\n V2_REQUEST_MAGIC.copy(buf, 0);\n buf[V2_REQUEST_MAGIC.length] = V2QueryType.CHALLENGE;\n return buf;\n}\n\n/**\n * Build a V2 request packet.\n * Format: Magic (8) + Type (1) + Token (32) + RequestID (4) + Flags (2) + Offset (4) + [AuthToken]\n */\nexport function buildV2Request(\n type: number,\n token: Buffer,\n requestId: number,\n flags: number,\n offset: number,\n authToken?: string\n): Buffer {\n const authBuf = authToken ? Buffer.from(authToken, 'utf8') : null;\n const authLength = authBuf ? 2 + authBuf.length : 0;\n const totalLength = V2_REQUEST_MAGIC.length + 1 + 32 + 4 + 2 + 4 + authLength;\n\n const buf = Buffer.alloc(totalLength);\n let pos = 0;\n\n // Magic\n V2_REQUEST_MAGIC.copy(buf, pos);\n pos += V2_REQUEST_MAGIC.length;\n\n // Type\n buf[pos++] = type;\n\n // Token\n token.copy(buf, pos);\n pos += 32;\n\n // Request ID (LE)\n buf.writeUInt32LE(requestId, pos);\n pos += 4;\n\n // Flags (LE)\n buf.writeUInt16LE(flags, pos);\n pos += 2;\n\n // Offset (LE)\n buf.writeUInt32LE(offset, pos);\n pos += 4;\n\n // Auth token (optional)\n if (authBuf) {\n buf.writeUInt16LE(authBuf.length, pos);\n pos += 2;\n authBuf.copy(buf, pos);\n }\n\n return buf;\n}\n\n/**\n * Parsed TLV entry.\n */\nexport interface TLVEntry {\n type: number;\n value: Buffer;\n}\n\n/**\n * Parsed V2 response header.\n */\nexport interface V2ResponseHeader {\n protocolVersion: number;\n flags: number;\n requestId: number;\n payloadLength: number;\n}\n\n/**\n * Validate V2 response magic bytes.\n */\nexport function validateV2Response(buf: Buffer): boolean {\n if (buf.length < V2_RESPONSE_MAGIC.length) {\n return false;\n }\n return buf.subarray(0, V2_RESPONSE_MAGIC.length).equals(V2_RESPONSE_MAGIC);\n}\n\n/**\n * Parse challenge response to extract 32-byte token.\n * Format: Magic (8) + Status (1) + Token (32) + [padding]\n */\nexport function parseChallengeResponse(buf: Buffer): Buffer {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n // Challenge response: Magic (8) + Status (1) + Token (32)\n const tokenOffset = V2_RESPONSE_MAGIC.length + 1; // Skip magic and status byte\n if (buf.length < tokenOffset + CHALLENGE_TOKEN_SIZE) {\n throw new Error('Invalid challenge response: too short');\n }\n\n return Buffer.from(buf.subarray(tokenOffset, tokenOffset + CHALLENGE_TOKEN_SIZE));\n}\n\n/**\n * Parse V2 response header.\n */\nexport function parseV2ResponseHeader(buf: Buffer): V2ResponseHeader {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(V2_RESPONSE_MAGIC.length);\n\n const protocolVersion = reader.readUInt8();\n const flags = reader.readUInt16LE();\n const requestId = reader.readUInt32LE();\n const payloadLength = reader.readUInt16LE();\n\n return { protocolVersion, flags, requestId, payloadLength };\n}\n\n/**\n * Parse TLV entries from payload.\n */\nexport function parseTLVEntries(payload: Buffer): TLVEntry[] {\n const entries: TLVEntry[] = [];\n const reader = new BufferReader(payload);\n\n while (reader.remaining >= 4) {\n const type = reader.readUInt16LE();\n const length = reader.readUInt16LE();\n\n if (reader.remaining < length) {\n throw new Error('Invalid TLV: truncated value');\n }\n\n const value = reader.readBytes(length);\n entries.push({ type, value });\n }\n\n return entries;\n}\n\n/**\n * Parse full V2 response (header + TLV payload).\n */\nexport function parseV2Response(buf: Buffer): { header: V2ResponseHeader; entries: TLVEntry[] } {\n const header = parseV2ResponseHeader(buf);\n\n // Header is 17 bytes: Magic (8) + Version (1) + Flags (2) + RequestID (4) + PayloadLength (2)\n const headerSize = V2_RESPONSE_MAGIC.length + 1 + 2 + 4 + 2;\n const payload = buf.subarray(headerSize, headerSize + header.payloadLength);\n\n const entries = parseTLVEntries(payload);\n\n return { header, entries };\n}\n\n/**\n * Parse SERVER_INFO TLV into ServerInfoV2.\n * Note: isNetwork is determined from response flags, not from the TLV data.\n */\nexport function parseTLVServerInfo(entry: TLVEntry, hasAddress: boolean, isNetwork: boolean): ServerInfoV2 {\n if (entry.type !== V2TLVType.SERVER_INFO) {\n throw new Error(`Expected SERVER_INFO TLV (${V2TLVType.SERVER_INFO}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n const result: ServerInfoV2 = {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n version,\n protocolVersion,\n protocolHash,\n isNetwork,\n };\n\n if (hasAddress && reader.remaining >= 2) {\n result.host = reader.readString();\n if (reader.remaining >= 2) {\n result.hostPort = reader.readUInt16LE();\n }\n }\n\n return result;\n}\n\n/**\n * Parsed player list from TLV.\n */\nexport interface ParsedPlayerList {\n players: Player[];\n totalPlayers: number;\n offset: number;\n}\n\n/**\n * Parse PLAYER_LIST TLV.\n * Format: totalPlayers (4) + playersInResponse (4) + offset (4) + player entries\n */\nexport function parseTLVPlayerList(entry: TLVEntry): ParsedPlayerList {\n if (entry.type !== V2TLVType.PLAYER_LIST) {\n throw new Error(`Expected PLAYER_LIST TLV (${V2TLVType.PLAYER_LIST}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const totalPlayers = reader.readInt32LE();\n const playersInResponse = reader.readInt32LE();\n const offset = reader.readInt32LE();\n\n const players: Player[] = [];\n for (let i = 0; i < playersInResponse; i++) {\n const name = reader.readString();\n const uuid = reader.readUUIDLE(); // UUIDs are little-endian in V2\n players.push({ name, uuid });\n }\n\n return { players, totalPlayers, offset };\n}\n\n/**\n * Find a TLV entry by type.\n */\nexport function findTLVEntry(entries: TLVEntry[], type: number): TLVEntry | undefined {\n return entries.find((e) => e.type === type);\n}\n"],"mappings":"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","names":["dgram","import_node_dgram","DEFAULT_TIMEOUT","dgram"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/query.ts","../src/protocol.ts","../src/query-v2.ts","../src/types-v2.ts","../src/protocol-v2.ts"],"sourcesContent":["export { query } from './query.js';\nexport { queryV2, clearChallengeCache } from './query-v2.js';\nexport type {\n ServerInfo,\n ServerInfoFull,\n Player,\n Plugin,\n QueryOptions,\n} from './types.js';\nexport type {\n ServerInfoV2,\n ServerInfoV2WithPlayers,\n QueryV2Options,\n} from './types-v2.js';\nexport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\n","import dgram from 'node:dgram';\nimport {\n buildRequest,\n parseBasicResponse,\n parseFullResponse,\n TYPE_BASIC,\n TYPE_FULL,\n} from './protocol.js';\nimport type { QueryOptions, ServerInfo, ServerInfoFull } from './types.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n/**\n * Send a UDP query and wait for response.\n */\nfunction sendQuery(\n host: string,\n port: number,\n type: number,\n timeout: number\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n const request = buildRequest(type);\n socket.send(request, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Query a Hytale server for information.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (full if options.full is true)\n *\n * @example\n * ```typescript\n * // Basic query\n * const info = await query('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // Full query (includes players + plugins)\n * const full = await query('play.example.com', 5520, { full: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function query(\n host: string,\n port?: number,\n options?: QueryOptions & { full?: false }\n): Promise<ServerInfo>;\nexport async function query(\n host: string,\n port: number,\n options: QueryOptions & { full: true }\n): Promise<ServerInfoFull>;\nexport async function query(\n host: string,\n port = 5520,\n options: QueryOptions & { full?: boolean } = {}\n): Promise<ServerInfo | ServerInfoFull> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const type = options.full ? TYPE_FULL : TYPE_BASIC;\n const response = await sendQuery(host, port, type, timeout);\n\n if (options.full) {\n return parseFullResponse(response);\n }\n return parseBasicResponse(response);\n}\n","import type { Player, Plugin, ServerInfo, ServerInfoFull } from './types.js';\n\n// Protocol constants\nexport const REQUEST_MAGIC = Buffer.from('HYQUERY\\0', 'ascii');\nexport const RESPONSE_MAGIC = Buffer.from('HYREPLY\\0', 'ascii');\nexport const TYPE_BASIC = 0x00;\nexport const TYPE_FULL = 0x01;\n\n// Capability flags (V1 extension)\nexport const CAP_V2_PROTOCOL = 0x01;\nexport const CAP_NETWORK_MODE = 0x02;\n\n/**\n * Build a query request packet.\n */\nexport function buildRequest(type: number): Buffer {\n const buf = Buffer.alloc(REQUEST_MAGIC.length + 1);\n REQUEST_MAGIC.copy(buf, 0);\n buf[REQUEST_MAGIC.length] = type;\n return buf;\n}\n\n/**\n * Buffer reader helper for parsing responses.\n */\nexport class BufferReader {\n private offset = 0;\n\n constructor(private buf: Buffer) {}\n\n private checkBounds(needed: number): void {\n if (this.offset + needed > this.buf.length) {\n throw new RangeError(\n `Buffer underflow: need ${needed} bytes at offset ${this.offset}, but buffer is ${this.buf.length} bytes`\n );\n }\n }\n\n readBytes(length: number): Buffer {\n this.checkBounds(length);\n const slice = this.buf.subarray(this.offset, this.offset + length);\n this.offset += length;\n return slice;\n }\n\n readUInt8(): number {\n this.checkBounds(1);\n return this.buf[this.offset++];\n }\n\n readUInt16LE(): number {\n this.checkBounds(2);\n const value = this.buf.readUInt16LE(this.offset);\n this.offset += 2;\n return value;\n }\n\n readUInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readUInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readBigInt64BE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64BE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readBoolean(): boolean {\n this.checkBounds(1);\n return this.buf[this.offset++] !== 0;\n }\n\n readString(): string {\n const length = this.readUInt16LE();\n if (length > this.remaining) {\n throw new RangeError(\n `Invalid string length ${length} at offset ${this.offset - 2}, only ${this.remaining} bytes remaining`\n );\n }\n const bytes = this.readBytes(length);\n return bytes.toString('utf8');\n }\n\n readUUID(): string {\n const msb = this.readBigInt64BE();\n const lsb = this.readBigInt64BE();\n return formatUUID(msb, lsb);\n }\n\n readBigInt64LE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64LE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readUUIDLE(): string {\n const msb = this.readBigInt64LE();\n const lsb = this.readBigInt64LE();\n return formatUUID(msb, lsb);\n }\n\n get remaining(): number {\n return this.buf.length - this.offset;\n }\n\n get position(): number {\n return this.offset;\n }\n}\n\n/**\n * Format UUID from most/least significant bits.\n */\nfunction formatUUID(msb: bigint, lsb: bigint): string {\n const toHex = (n: bigint): string => {\n if (n < 0n) {\n n = BigInt.asUintN(64, n);\n }\n return n.toString(16).padStart(16, '0');\n };\n\n const hex = toHex(msb) + toHex(lsb);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Validate response magic bytes.\n */\nexport function validateResponse(buf: Buffer): boolean {\n if (buf.length < RESPONSE_MAGIC.length + 1) {\n return false;\n }\n return buf.subarray(0, RESPONSE_MAGIC.length).equals(RESPONSE_MAGIC);\n}\n\n/**\n * Parse capability extension from remaining bytes (3 bytes: 2 caps + 1 version).\n * Only parses if exactly 3 bytes remain to avoid misinterpreting other data.\n */\nfunction parseCapabilities(reader: BufferReader): {\n supportsV2: boolean;\n isNetworkMode: boolean;\n v2Version: number;\n} {\n if (reader.remaining === 3) {\n const caps = reader.readUInt16LE();\n const v2Version = reader.readUInt8();\n return {\n supportsV2: (caps & CAP_V2_PROTOCOL) !== 0,\n isNetworkMode: (caps & CAP_NETWORK_MODE) !== 0,\n v2Version,\n };\n }\n return { supportsV2: false, isNetworkMode: false, v2Version: 0 };\n}\n\n/**\n * Check if hostPort is encoded as int32 (legacy) vs uint16 (extended).\n * Returns true if int32, false if uint16.\n */\nfunction isLegacyPortFormat(buf: Buffer, offsetAfterMaxPlayers: number): boolean {\n const versionLenOffset = offsetAfterMaxPlayers + 2; // assuming uint16 port\n if (versionLenOffset + 2 > buf.length) return true;\n\n const versionLen = buf.readUInt16LE(versionLenOffset);\n // If version length is 0 or invalid, port is likely int32 (legacy format)\n return versionLen === 0 || versionLen > 500;\n}\n\n/**\n * Parse a basic query response.\n * Supports both extended format (uint16 port + protocolVersion/Hash) and legacy format (int32 port).\n */\nexport function parseBasicResponse(buf: Buffer): ServerInfo {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic + type\n reader.readBytes(RESPONSE_MAGIC.length + 1);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n\n // Detect format: legacy uses int32 for port, extended uses uint16\n const legacy = isLegacyPortFormat(buf, reader.position);\n\n const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = legacy ? 0 : reader.readInt32LE();\n const protocolHash = legacy ? '' : reader.readString();\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n ...capabilities,\n };\n}\n\n/**\n * Parse a full query response.\n * Supports both extended and legacy formats.\n */\nexport function parseFullResponse(buf: Buffer): ServerInfoFull {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic + type\n reader.readBytes(RESPONSE_MAGIC.length + 1);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n\n // Detect format\n const legacy = isLegacyPortFormat(buf, reader.position);\n\n const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = legacy ? 0 : reader.readInt32LE();\n const protocolHash = legacy ? '' : reader.readString();\n\n // Player list\n const playerCount = reader.readInt32LE();\n const players: Player[] = [];\n for (let i = 0; i < playerCount; i++) {\n players.push({\n name: reader.readString(),\n uuid: reader.readUUID(),\n });\n }\n\n // Plugin list\n const pluginCount = reader.readInt32LE();\n const plugins: Plugin[] = [];\n for (let i = 0; i < pluginCount; i++) {\n plugins.push({\n id: reader.readString(),\n version: reader.readString(),\n enabled: reader.readBoolean(),\n });\n }\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n players,\n plugins,\n ...capabilities,\n };\n}\n","import dgram from 'node:dgram';\nimport type { Player } from './types.js';\nimport type { QueryV2Options, ServerInfoV2, ServerInfoV2WithPlayers } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport {\n buildChallengeRequest,\n buildV2Request,\n findTLVEntry,\n parseChallengeResponse,\n parseTLVPlayerList,\n parseTLVServerInfo,\n parseV2Response,\n} from './protocol-v2.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n// Challenge token cache configuration\nconst TOKEN_TTL_MS = 30_000;\nconst TOKEN_REFRESH_BUFFER_MS = 5_000;\nconst CLEANUP_INTERVAL_MS = 60_000;\n\ninterface CachedChallenge {\n token: Buffer;\n expiresAt: number;\n requestId: number;\n}\n\n// Module-level cache\nconst challengeCache = new Map<string, CachedChallenge>();\nlet cleanupScheduled = false;\n\nfunction scheduleCleanup(): void {\n if (cleanupScheduled) return;\n cleanupScheduled = true;\n\n setTimeout(() => {\n const now = Date.now();\n for (const [key, entry] of challengeCache) {\n if (now >= entry.expiresAt) {\n challengeCache.delete(key);\n }\n }\n cleanupScheduled = false;\n if (challengeCache.size > 0) {\n scheduleCleanup();\n }\n }, CLEANUP_INTERVAL_MS).unref();\n}\n\n/**\n * Clear the challenge token cache.\n * Useful for testing or forcing fresh challenges.\n */\nexport function clearChallengeCache(): void {\n challengeCache.clear();\n}\n\n/**\n * Send a UDP packet and wait for response.\n */\nfunction sendUDP(host: string, port: number, data: Buffer, timeout: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n socket.send(data, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Fetch a new challenge token from the server.\n */\nasync function fetchChallenge(host: string, port: number, timeout: number): Promise<Buffer> {\n const request = buildChallengeRequest();\n const response = await sendUDP(host, port, request, timeout);\n return parseChallengeResponse(response);\n}\n\n/**\n * Get or refresh a challenge token for a host:port.\n */\nasync function ensureChallenge(\n host: string,\n port: number,\n timeout: number\n): Promise<CachedChallenge> {\n const key = `${host}:${port}`;\n const cached = challengeCache.get(key);\n const now = Date.now();\n\n // Return cached if still valid (with buffer time)\n if (cached && now < cached.expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n cached.requestId++;\n return cached;\n }\n\n // Fetch new challenge token\n const token = await fetchChallenge(host, port, timeout);\n const entry: CachedChallenge = {\n token,\n expiresAt: now + TOKEN_TTL_MS,\n requestId: 1,\n };\n\n challengeCache.set(key, entry);\n scheduleCleanup();\n\n return entry;\n}\n\n/**\n * Perform a V2 query request.\n */\nasync function performV2Query(\n host: string,\n port: number,\n type: number,\n offset: number,\n timeout: number,\n authToken?: string\n): Promise<{ header: ReturnType<typeof parseV2Response>['header']; entries: ReturnType<typeof parseV2Response>['entries'] }> {\n const challenge = await ensureChallenge(host, port, timeout);\n\n const request = buildV2Request(type, challenge.token, challenge.requestId, 0, offset, authToken);\n const response = await sendUDP(host, port, request, timeout);\n\n return parseV2Response(response);\n}\n\n/**\n * Query a Hytale server using V2 protocol (basic info only).\n */\nexport async function queryV2(\n host: string,\n port?: number,\n options?: QueryV2Options & { players?: false }\n): Promise<ServerInfoV2>;\n\n/**\n * Query a Hytale server using V2 protocol with player list.\n */\nexport async function queryV2(\n host: string,\n port: number,\n options: QueryV2Options & { players: true | 'all' }\n): Promise<ServerInfoV2WithPlayers>;\n\n/**\n * Query a Hytale server using V2 protocol.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (with players if requested)\n *\n * @example\n * ```typescript\n * // Basic V2 query\n * const info = await queryV2('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // V2 query with players\n * const full = await queryV2('play.example.com', 5520, { players: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n *\n * // V2 query with all players (auto-pagination)\n * const all = await queryV2('play.example.com', 5520, { players: 'all' });\n * console.log(`All ${all.totalPlayers} players:`, all.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function queryV2(\n host: string,\n port = 5520,\n options: QueryV2Options = {}\n): Promise<ServerInfoV2 | ServerInfoV2WithPlayers> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const wantPlayers = options.players === true || options.players === 'all';\n const offset = options.playerOffset ?? 0;\n\n // First, always do a BASIC query to get server info\n const { header: basicHeader, entries: basicEntries } = await performV2Query(\n host,\n port,\n V2QueryType.BASIC,\n 0,\n timeout,\n options.authToken\n );\n\n const hasAddress = (basicHeader.flags & V2ResponseFlag.HAS_ADDRESS) !== 0;\n const isNetwork = (basicHeader.flags & V2ResponseFlag.IS_NETWORK) !== 0;\n\n // Parse server info\n const serverInfoEntry = findTLVEntry(basicEntries, V2TLVType.SERVER_INFO);\n if (!serverInfoEntry) {\n throw new Error('Response missing SERVER_INFO TLV');\n }\n const serverInfo = parseTLVServerInfo(serverInfoEntry, hasAddress, isNetwork);\n\n // If players not requested, return basic info\n if (!wantPlayers) {\n return serverInfo;\n }\n\n // Do a PLAYERS query to get player list\n const { header: playersHeader, entries: playersEntries } = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n offset,\n timeout,\n options.authToken\n );\n\n const hasMorePlayers = (playersHeader.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n\n // Parse player list\n const playerListEntry = findTLVEntry(playersEntries, V2TLVType.PLAYER_LIST);\n if (!playerListEntry) {\n throw new Error('Response missing PLAYER_LIST TLV');\n }\n const playerList = parseTLVPlayerList(playerListEntry);\n\n let players = playerList.players;\n let currentOffset = playerList.offset + players.length;\n let hasMore = hasMorePlayers;\n\n // If 'all' mode, paginate through remaining players\n if (options.players === 'all' && hasMore) {\n const allPlayers: Player[] = [...players];\n\n while (hasMore) {\n const nextResult = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n currentOffset,\n timeout,\n options.authToken\n );\n\n const nextHasMore = (nextResult.header.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n const nextPlayerEntry = findTLVEntry(nextResult.entries, V2TLVType.PLAYER_LIST);\n\n if (!nextPlayerEntry) {\n break;\n }\n\n const nextPlayerList = parseTLVPlayerList(nextPlayerEntry);\n allPlayers.push(...nextPlayerList.players);\n currentOffset = nextPlayerList.offset + nextPlayerList.players.length;\n hasMore = nextHasMore;\n }\n\n players = allPlayers;\n hasMore = false;\n }\n\n return {\n ...serverInfo,\n players,\n totalPlayers: playerList.totalPlayers,\n offset: playerList.offset,\n hasMore,\n };\n}\n","import type { Player } from './types.js';\n\n/**\n * V2 query type constants.\n */\nexport const V2QueryType = {\n CHALLENGE: 0x00,\n BASIC: 0x01,\n PLAYERS: 0x02,\n} as const;\n\n/**\n * V2 response flag constants.\n */\nexport const V2ResponseFlag = {\n HAS_MORE_PLAYERS: 0x0001,\n AUTH_REQUIRED: 0x0002,\n IS_NETWORK: 0x0010,\n HAS_ADDRESS: 0x0020,\n} as const;\n\n/**\n * V2 TLV type constants.\n */\nexport const V2TLVType = {\n SERVER_INFO: 0x0001,\n PLAYER_LIST: 0x0002,\n} as const;\n\n/**\n * Basic server information from V2 protocol.\n */\nexport interface ServerInfoV2 {\n /** Server display name */\n serverName: string;\n /** Message of the day */\n motd: string;\n /** Current number of players online */\n currentPlayers: number;\n /** Maximum player capacity */\n maxPlayers: number;\n /** Server version string */\n version: string;\n /** Protocol version number */\n protocolVersion: number;\n /** Protocol hash string */\n protocolHash: string;\n /** Server host (if FLAG_RESPONSE_HAS_ADDRESS) */\n host?: string;\n /** Server port (if FLAG_RESPONSE_HAS_ADDRESS) */\n hostPort?: number;\n /** Whether the server is in network aggregation mode */\n isNetwork: boolean;\n}\n\n/**\n * V2 server info combined with player list.\n */\nexport interface ServerInfoV2WithPlayers extends ServerInfoV2 {\n /** List of online players */\n players: Player[];\n /** Total players on server */\n totalPlayers: number;\n /** Offset used for this response */\n offset: number;\n /** Whether more players are available */\n hasMore: boolean;\n}\n\n/**\n * Options for V2 query function.\n */\nexport interface QueryV2Options {\n /** Timeout in milliseconds (default: 5000) */\n timeout?: number;\n /** Request player list: false=basic only, true=single page, 'all'=paginate all */\n players?: boolean | 'all';\n /** Starting offset for player pagination */\n playerOffset?: number;\n /** Optional auth token for private servers */\n authToken?: string;\n}\n","import type { Player } from './types.js';\nimport type { ServerInfoV2 } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport { BufferReader } from './protocol.js';\n\n// Magic bytes for V2 protocol\nexport const V2_REQUEST_MAGIC = Buffer.from('ONEQUERY', 'ascii');\nexport const V2_RESPONSE_MAGIC = Buffer.from('ONEREPLY', 'ascii');\n\n// Challenge token size\nexport const CHALLENGE_TOKEN_SIZE = 32;\n\n/**\n * Build a challenge request packet.\n * Format: Magic (8) + Type (1)\n */\nexport function buildChallengeRequest(): Buffer {\n const buf = Buffer.alloc(V2_REQUEST_MAGIC.length + 1);\n V2_REQUEST_MAGIC.copy(buf, 0);\n buf[V2_REQUEST_MAGIC.length] = V2QueryType.CHALLENGE;\n return buf;\n}\n\n/**\n * Build a V2 request packet.\n * Format: Magic (8) + Type (1) + Token (32) + RequestID (4) + Flags (2) + Offset (4) + [AuthToken]\n */\nexport function buildV2Request(\n type: number,\n token: Buffer,\n requestId: number,\n flags: number,\n offset: number,\n authToken?: string\n): Buffer {\n const authBuf = authToken ? Buffer.from(authToken, 'utf8') : null;\n const authLength = authBuf ? 2 + authBuf.length : 0;\n const totalLength = V2_REQUEST_MAGIC.length + 1 + 32 + 4 + 2 + 4 + authLength;\n\n const buf = Buffer.alloc(totalLength);\n let pos = 0;\n\n // Magic\n V2_REQUEST_MAGIC.copy(buf, pos);\n pos += V2_REQUEST_MAGIC.length;\n\n // Type\n buf[pos++] = type;\n\n // Token\n token.copy(buf, pos);\n pos += 32;\n\n // Request ID (LE)\n buf.writeUInt32LE(requestId, pos);\n pos += 4;\n\n // Flags (LE)\n buf.writeUInt16LE(flags, pos);\n pos += 2;\n\n // Offset (LE)\n buf.writeUInt32LE(offset, pos);\n pos += 4;\n\n // Auth token (optional)\n if (authBuf) {\n buf.writeUInt16LE(authBuf.length, pos);\n pos += 2;\n authBuf.copy(buf, pos);\n }\n\n return buf;\n}\n\n/**\n * Parsed TLV entry.\n */\nexport interface TLVEntry {\n type: number;\n value: Buffer;\n}\n\n/**\n * Parsed V2 response header.\n */\nexport interface V2ResponseHeader {\n protocolVersion: number;\n flags: number;\n requestId: number;\n payloadLength: number;\n}\n\n/**\n * Validate V2 response magic bytes.\n */\nexport function validateV2Response(buf: Buffer): boolean {\n if (buf.length < V2_RESPONSE_MAGIC.length) {\n return false;\n }\n return buf.subarray(0, V2_RESPONSE_MAGIC.length).equals(V2_RESPONSE_MAGIC);\n}\n\n/**\n * Parse challenge response to extract 32-byte token.\n * Format: Magic (8) + Status (1) + Token (32) + [padding]\n */\nexport function parseChallengeResponse(buf: Buffer): Buffer {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n // Challenge response: Magic (8) + Status (1) + Token (32)\n const tokenOffset = V2_RESPONSE_MAGIC.length + 1; // Skip magic and status byte\n if (buf.length < tokenOffset + CHALLENGE_TOKEN_SIZE) {\n throw new Error('Invalid challenge response: too short');\n }\n\n return Buffer.from(buf.subarray(tokenOffset, tokenOffset + CHALLENGE_TOKEN_SIZE));\n}\n\n/**\n * Parse V2 response header.\n */\nexport function parseV2ResponseHeader(buf: Buffer): V2ResponseHeader {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(V2_RESPONSE_MAGIC.length);\n\n const protocolVersion = reader.readUInt8();\n const flags = reader.readUInt16LE();\n const requestId = reader.readUInt32LE();\n const payloadLength = reader.readUInt16LE();\n\n return { protocolVersion, flags, requestId, payloadLength };\n}\n\n/**\n * Parse TLV entries from payload.\n */\nexport function parseTLVEntries(payload: Buffer): TLVEntry[] {\n const entries: TLVEntry[] = [];\n const reader = new BufferReader(payload);\n\n while (reader.remaining >= 4) {\n const type = reader.readUInt16LE();\n const length = reader.readUInt16LE();\n\n if (reader.remaining < length) {\n throw new Error('Invalid TLV: truncated value');\n }\n\n const value = reader.readBytes(length);\n entries.push({ type, value });\n }\n\n return entries;\n}\n\n/**\n * Parse full V2 response (header + TLV payload).\n */\nexport function parseV2Response(buf: Buffer): { header: V2ResponseHeader; entries: TLVEntry[] } {\n const header = parseV2ResponseHeader(buf);\n\n // Header is 17 bytes: Magic (8) + Version (1) + Flags (2) + RequestID (4) + PayloadLength (2)\n const headerSize = V2_RESPONSE_MAGIC.length + 1 + 2 + 4 + 2;\n const payload = buf.subarray(headerSize, headerSize + header.payloadLength);\n\n const entries = parseTLVEntries(payload);\n\n return { header, entries };\n}\n\n/**\n * Parse SERVER_INFO TLV into ServerInfoV2.\n * Note: isNetwork is determined from response flags, not from the TLV data.\n */\nexport function parseTLVServerInfo(entry: TLVEntry, hasAddress: boolean, isNetwork: boolean): ServerInfoV2 {\n if (entry.type !== V2TLVType.SERVER_INFO) {\n throw new Error(`Expected SERVER_INFO TLV (${V2TLVType.SERVER_INFO}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n const result: ServerInfoV2 = {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n version,\n protocolVersion,\n protocolHash,\n isNetwork,\n };\n\n if (hasAddress && reader.remaining >= 2) {\n result.host = reader.readString();\n if (reader.remaining >= 2) {\n result.hostPort = reader.readUInt16LE();\n }\n }\n\n return result;\n}\n\n/**\n * Parsed player list from TLV.\n */\nexport interface ParsedPlayerList {\n players: Player[];\n totalPlayers: number;\n offset: number;\n}\n\n/**\n * Parse PLAYER_LIST TLV.\n * Format: totalPlayers (4) + playersInResponse (4) + offset (4) + player entries\n */\nexport function parseTLVPlayerList(entry: TLVEntry): ParsedPlayerList {\n if (entry.type !== V2TLVType.PLAYER_LIST) {\n throw new Error(`Expected PLAYER_LIST TLV (${V2TLVType.PLAYER_LIST}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const totalPlayers = reader.readInt32LE();\n const playersInResponse = reader.readInt32LE();\n const offset = reader.readInt32LE();\n\n const players: Player[] = [];\n for (let i = 0; i < playersInResponse; i++) {\n const name = reader.readString();\n const uuid = reader.readUUIDLE(); // UUIDs are little-endian in V2\n players.push({ name, uuid });\n }\n\n return { players, totalPlayers, offset };\n}\n\n/**\n * Find a TLV entry by type.\n */\nexport function findTLVEntry(entries: TLVEntry[], type: number): TLVEntry | undefined {\n return entries.find((e) => e.type === type);\n}\n"],"mappings":"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","names":["dgram","import_node_dgram","DEFAULT_TIMEOUT","dgram"]}
|
package/dist/index.js
CHANGED
|
@@ -114,7 +114,7 @@ function validateResponse(buf) {
|
|
|
114
114
|
return buf.subarray(0, RESPONSE_MAGIC.length).equals(RESPONSE_MAGIC);
|
|
115
115
|
}
|
|
116
116
|
function parseCapabilities(reader) {
|
|
117
|
-
if (reader.remaining
|
|
117
|
+
if (reader.remaining === 3) {
|
|
118
118
|
const caps = reader.readUInt16LE();
|
|
119
119
|
const v2Version = reader.readUInt8();
|
|
120
120
|
return {
|
|
@@ -125,21 +125,27 @@ function parseCapabilities(reader) {
|
|
|
125
125
|
}
|
|
126
126
|
return { supportsV2: false, isNetworkMode: false, v2Version: 0 };
|
|
127
127
|
}
|
|
128
|
+
function isLegacyPortFormat(buf, offsetAfterMaxPlayers) {
|
|
129
|
+
const versionLenOffset = offsetAfterMaxPlayers + 2;
|
|
130
|
+
if (versionLenOffset + 2 > buf.length) return true;
|
|
131
|
+
const versionLen = buf.readUInt16LE(versionLenOffset);
|
|
132
|
+
return versionLen === 0 || versionLen > 500;
|
|
133
|
+
}
|
|
128
134
|
function parseBasicResponse(buf) {
|
|
129
135
|
if (!validateResponse(buf)) {
|
|
130
136
|
throw new Error("Invalid response: magic mismatch");
|
|
131
137
|
}
|
|
132
138
|
const reader = new BufferReader(buf);
|
|
133
|
-
reader.readBytes(RESPONSE_MAGIC.length);
|
|
134
|
-
reader.readBytes(1);
|
|
139
|
+
reader.readBytes(RESPONSE_MAGIC.length + 1);
|
|
135
140
|
const serverName = reader.readString();
|
|
136
141
|
const motd = reader.readString();
|
|
137
142
|
const currentPlayers = reader.readInt32LE();
|
|
138
143
|
const maxPlayers = reader.readInt32LE();
|
|
139
|
-
const
|
|
144
|
+
const legacy = isLegacyPortFormat(buf, reader.position);
|
|
145
|
+
const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();
|
|
140
146
|
const version = reader.readString();
|
|
141
|
-
const protocolVersion = reader.readInt32LE();
|
|
142
|
-
const protocolHash = reader.readString();
|
|
147
|
+
const protocolVersion = legacy ? 0 : reader.readInt32LE();
|
|
148
|
+
const protocolHash = legacy ? "" : reader.readString();
|
|
143
149
|
const capabilities = parseCapabilities(reader);
|
|
144
150
|
return {
|
|
145
151
|
serverName,
|
|
@@ -158,16 +164,16 @@ function parseFullResponse(buf) {
|
|
|
158
164
|
throw new Error("Invalid response: magic mismatch");
|
|
159
165
|
}
|
|
160
166
|
const reader = new BufferReader(buf);
|
|
161
|
-
reader.readBytes(RESPONSE_MAGIC.length);
|
|
162
|
-
reader.readBytes(1);
|
|
167
|
+
reader.readBytes(RESPONSE_MAGIC.length + 1);
|
|
163
168
|
const serverName = reader.readString();
|
|
164
169
|
const motd = reader.readString();
|
|
165
170
|
const currentPlayers = reader.readInt32LE();
|
|
166
171
|
const maxPlayers = reader.readInt32LE();
|
|
167
|
-
const
|
|
172
|
+
const legacy = isLegacyPortFormat(buf, reader.position);
|
|
173
|
+
const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();
|
|
168
174
|
const version = reader.readString();
|
|
169
|
-
const protocolVersion = reader.readInt32LE();
|
|
170
|
-
const protocolHash = reader.readString();
|
|
175
|
+
const protocolVersion = legacy ? 0 : reader.readInt32LE();
|
|
176
|
+
const protocolHash = legacy ? "" : reader.readString();
|
|
171
177
|
const playerCount = reader.readInt32LE();
|
|
172
178
|
const players = [];
|
|
173
179
|
for (let i = 0; i < playerCount; i++) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/query.ts","../src/protocol.ts","../src/query-v2.ts","../src/types-v2.ts","../src/protocol-v2.ts"],"sourcesContent":["import dgram from 'node:dgram';\nimport {\n buildRequest,\n parseBasicResponse,\n parseFullResponse,\n TYPE_BASIC,\n TYPE_FULL,\n} from './protocol.js';\nimport type { QueryOptions, ServerInfo, ServerInfoFull } from './types.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n/**\n * Send a UDP query and wait for response.\n */\nfunction sendQuery(\n host: string,\n port: number,\n type: number,\n timeout: number\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n const request = buildRequest(type);\n socket.send(request, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Query a Hytale server for information.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (full if options.full is true)\n *\n * @example\n * ```typescript\n * // Basic query\n * const info = await query('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // Full query (includes players + plugins)\n * const full = await query('play.example.com', 5520, { full: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function query(\n host: string,\n port?: number,\n options?: QueryOptions & { full?: false }\n): Promise<ServerInfo>;\nexport async function query(\n host: string,\n port: number,\n options: QueryOptions & { full: true }\n): Promise<ServerInfoFull>;\nexport async function query(\n host: string,\n port = 5520,\n options: QueryOptions & { full?: boolean } = {}\n): Promise<ServerInfo | ServerInfoFull> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const type = options.full ? TYPE_FULL : TYPE_BASIC;\n const response = await sendQuery(host, port, type, timeout);\n\n if (options.full) {\n return parseFullResponse(response);\n }\n return parseBasicResponse(response);\n}\n","import type { Player, Plugin, ServerInfo, ServerInfoFull } from './types.js';\n\n// Protocol constants\nexport const REQUEST_MAGIC = Buffer.from('HYQUERY\\0', 'ascii');\nexport const RESPONSE_MAGIC = Buffer.from('HYREPLY\\0', 'ascii');\nexport const TYPE_BASIC = 0x00;\nexport const TYPE_FULL = 0x01;\n\n// Capability flags (V1 extension)\nexport const CAP_V2_PROTOCOL = 0x01;\nexport const CAP_NETWORK_MODE = 0x02;\n\n/**\n * Build a query request packet.\n */\nexport function buildRequest(type: number): Buffer {\n const buf = Buffer.alloc(REQUEST_MAGIC.length + 1);\n REQUEST_MAGIC.copy(buf, 0);\n buf[REQUEST_MAGIC.length] = type;\n return buf;\n}\n\n/**\n * Buffer reader helper for parsing responses.\n */\nexport class BufferReader {\n private offset = 0;\n\n constructor(private buf: Buffer) {}\n\n private checkBounds(needed: number): void {\n if (this.offset + needed > this.buf.length) {\n throw new RangeError(\n `Buffer underflow: need ${needed} bytes at offset ${this.offset}, but buffer is ${this.buf.length} bytes`\n );\n }\n }\n\n readBytes(length: number): Buffer {\n this.checkBounds(length);\n const slice = this.buf.subarray(this.offset, this.offset + length);\n this.offset += length;\n return slice;\n }\n\n readUInt8(): number {\n this.checkBounds(1);\n return this.buf[this.offset++];\n }\n\n readUInt16LE(): number {\n this.checkBounds(2);\n const value = this.buf.readUInt16LE(this.offset);\n this.offset += 2;\n return value;\n }\n\n readUInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readUInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readBigInt64BE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64BE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readBoolean(): boolean {\n this.checkBounds(1);\n return this.buf[this.offset++] !== 0;\n }\n\n readString(): string {\n const length = this.readUInt16LE();\n if (length > this.remaining) {\n throw new RangeError(\n `Invalid string length ${length} at offset ${this.offset - 2}, only ${this.remaining} bytes remaining`\n );\n }\n const bytes = this.readBytes(length);\n return bytes.toString('utf8');\n }\n\n readUUID(): string {\n const msb = this.readBigInt64BE();\n const lsb = this.readBigInt64BE();\n return formatUUID(msb, lsb);\n }\n\n readBigInt64LE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64LE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readUUIDLE(): string {\n const msb = this.readBigInt64LE();\n const lsb = this.readBigInt64LE();\n return formatUUID(msb, lsb);\n }\n\n get remaining(): number {\n return this.buf.length - this.offset;\n }\n\n get position(): number {\n return this.offset;\n }\n}\n\n/**\n * Format UUID from most/least significant bits.\n */\nfunction formatUUID(msb: bigint, lsb: bigint): string {\n const toHex = (n: bigint): string => {\n if (n < 0n) {\n n = BigInt.asUintN(64, n);\n }\n return n.toString(16).padStart(16, '0');\n };\n\n const hex = toHex(msb) + toHex(lsb);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Validate response magic bytes.\n */\nexport function validateResponse(buf: Buffer): boolean {\n if (buf.length < RESPONSE_MAGIC.length + 1) {\n return false;\n }\n return buf.subarray(0, RESPONSE_MAGIC.length).equals(RESPONSE_MAGIC);\n}\n\n/**\n * Parse capability extension from remaining bytes (3 bytes: 2 caps + 1 version).\n */\nfunction parseCapabilities(reader: BufferReader): {\n supportsV2: boolean;\n isNetworkMode: boolean;\n v2Version: number;\n} {\n if (reader.remaining >= 3) {\n const caps = reader.readUInt16LE();\n const v2Version = reader.readUInt8();\n return {\n supportsV2: (caps & CAP_V2_PROTOCOL) !== 0,\n isNetworkMode: (caps & CAP_NETWORK_MODE) !== 0,\n v2Version,\n };\n }\n return { supportsV2: false, isNetworkMode: false, v2Version: 0 };\n}\n\n/**\n * Parse a basic query response.\n */\nexport function parseBasicResponse(buf: Buffer): ServerInfo {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(RESPONSE_MAGIC.length);\n\n // Skip type\n reader.readBytes(1);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const hostPort = reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n ...capabilities,\n };\n}\n\n/**\n * Parse a full query response.\n */\nexport function parseFullResponse(buf: Buffer): ServerInfoFull {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(RESPONSE_MAGIC.length);\n\n // Skip type\n reader.readBytes(1);\n\n // Base info\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const hostPort = reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n // Player list\n const playerCount = reader.readInt32LE();\n const players: Player[] = [];\n for (let i = 0; i < playerCount; i++) {\n players.push({\n name: reader.readString(),\n uuid: reader.readUUID(),\n });\n }\n\n // Plugin list\n const pluginCount = reader.readInt32LE();\n const plugins: Plugin[] = [];\n for (let i = 0; i < pluginCount; i++) {\n plugins.push({\n id: reader.readString(),\n version: reader.readString(),\n enabled: reader.readBoolean(),\n });\n }\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n players,\n plugins,\n ...capabilities,\n };\n}\n","import dgram from 'node:dgram';\nimport type { Player } from './types.js';\nimport type { QueryV2Options, ServerInfoV2, ServerInfoV2WithPlayers } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport {\n buildChallengeRequest,\n buildV2Request,\n findTLVEntry,\n parseChallengeResponse,\n parseTLVPlayerList,\n parseTLVServerInfo,\n parseV2Response,\n} from './protocol-v2.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n// Challenge token cache configuration\nconst TOKEN_TTL_MS = 30_000;\nconst TOKEN_REFRESH_BUFFER_MS = 5_000;\nconst CLEANUP_INTERVAL_MS = 60_000;\n\ninterface CachedChallenge {\n token: Buffer;\n expiresAt: number;\n requestId: number;\n}\n\n// Module-level cache\nconst challengeCache = new Map<string, CachedChallenge>();\nlet cleanupScheduled = false;\n\nfunction scheduleCleanup(): void {\n if (cleanupScheduled) return;\n cleanupScheduled = true;\n\n setTimeout(() => {\n const now = Date.now();\n for (const [key, entry] of challengeCache) {\n if (now >= entry.expiresAt) {\n challengeCache.delete(key);\n }\n }\n cleanupScheduled = false;\n if (challengeCache.size > 0) {\n scheduleCleanup();\n }\n }, CLEANUP_INTERVAL_MS).unref();\n}\n\n/**\n * Clear the challenge token cache.\n * Useful for testing or forcing fresh challenges.\n */\nexport function clearChallengeCache(): void {\n challengeCache.clear();\n}\n\n/**\n * Send a UDP packet and wait for response.\n */\nfunction sendUDP(host: string, port: number, data: Buffer, timeout: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n socket.send(data, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Fetch a new challenge token from the server.\n */\nasync function fetchChallenge(host: string, port: number, timeout: number): Promise<Buffer> {\n const request = buildChallengeRequest();\n const response = await sendUDP(host, port, request, timeout);\n return parseChallengeResponse(response);\n}\n\n/**\n * Get or refresh a challenge token for a host:port.\n */\nasync function ensureChallenge(\n host: string,\n port: number,\n timeout: number\n): Promise<CachedChallenge> {\n const key = `${host}:${port}`;\n const cached = challengeCache.get(key);\n const now = Date.now();\n\n // Return cached if still valid (with buffer time)\n if (cached && now < cached.expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n cached.requestId++;\n return cached;\n }\n\n // Fetch new challenge token\n const token = await fetchChallenge(host, port, timeout);\n const entry: CachedChallenge = {\n token,\n expiresAt: now + TOKEN_TTL_MS,\n requestId: 1,\n };\n\n challengeCache.set(key, entry);\n scheduleCleanup();\n\n return entry;\n}\n\n/**\n * Perform a V2 query request.\n */\nasync function performV2Query(\n host: string,\n port: number,\n type: number,\n offset: number,\n timeout: number,\n authToken?: string\n): Promise<{ header: ReturnType<typeof parseV2Response>['header']; entries: ReturnType<typeof parseV2Response>['entries'] }> {\n const challenge = await ensureChallenge(host, port, timeout);\n\n const request = buildV2Request(type, challenge.token, challenge.requestId, 0, offset, authToken);\n const response = await sendUDP(host, port, request, timeout);\n\n return parseV2Response(response);\n}\n\n/**\n * Query a Hytale server using V2 protocol (basic info only).\n */\nexport async function queryV2(\n host: string,\n port?: number,\n options?: QueryV2Options & { players?: false }\n): Promise<ServerInfoV2>;\n\n/**\n * Query a Hytale server using V2 protocol with player list.\n */\nexport async function queryV2(\n host: string,\n port: number,\n options: QueryV2Options & { players: true | 'all' }\n): Promise<ServerInfoV2WithPlayers>;\n\n/**\n * Query a Hytale server using V2 protocol.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (with players if requested)\n *\n * @example\n * ```typescript\n * // Basic V2 query\n * const info = await queryV2('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // V2 query with players\n * const full = await queryV2('play.example.com', 5520, { players: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n *\n * // V2 query with all players (auto-pagination)\n * const all = await queryV2('play.example.com', 5520, { players: 'all' });\n * console.log(`All ${all.totalPlayers} players:`, all.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function queryV2(\n host: string,\n port = 5520,\n options: QueryV2Options = {}\n): Promise<ServerInfoV2 | ServerInfoV2WithPlayers> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const wantPlayers = options.players === true || options.players === 'all';\n const offset = options.playerOffset ?? 0;\n\n // First, always do a BASIC query to get server info\n const { header: basicHeader, entries: basicEntries } = await performV2Query(\n host,\n port,\n V2QueryType.BASIC,\n 0,\n timeout,\n options.authToken\n );\n\n const hasAddress = (basicHeader.flags & V2ResponseFlag.HAS_ADDRESS) !== 0;\n const isNetwork = (basicHeader.flags & V2ResponseFlag.IS_NETWORK) !== 0;\n\n // Parse server info\n const serverInfoEntry = findTLVEntry(basicEntries, V2TLVType.SERVER_INFO);\n if (!serverInfoEntry) {\n throw new Error('Response missing SERVER_INFO TLV');\n }\n const serverInfo = parseTLVServerInfo(serverInfoEntry, hasAddress, isNetwork);\n\n // If players not requested, return basic info\n if (!wantPlayers) {\n return serverInfo;\n }\n\n // Do a PLAYERS query to get player list\n const { header: playersHeader, entries: playersEntries } = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n offset,\n timeout,\n options.authToken\n );\n\n const hasMorePlayers = (playersHeader.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n\n // Parse player list\n const playerListEntry = findTLVEntry(playersEntries, V2TLVType.PLAYER_LIST);\n if (!playerListEntry) {\n throw new Error('Response missing PLAYER_LIST TLV');\n }\n const playerList = parseTLVPlayerList(playerListEntry);\n\n let players = playerList.players;\n let currentOffset = playerList.offset + players.length;\n let hasMore = hasMorePlayers;\n\n // If 'all' mode, paginate through remaining players\n if (options.players === 'all' && hasMore) {\n const allPlayers: Player[] = [...players];\n\n while (hasMore) {\n const nextResult = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n currentOffset,\n timeout,\n options.authToken\n );\n\n const nextHasMore = (nextResult.header.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n const nextPlayerEntry = findTLVEntry(nextResult.entries, V2TLVType.PLAYER_LIST);\n\n if (!nextPlayerEntry) {\n break;\n }\n\n const nextPlayerList = parseTLVPlayerList(nextPlayerEntry);\n allPlayers.push(...nextPlayerList.players);\n currentOffset = nextPlayerList.offset + nextPlayerList.players.length;\n hasMore = nextHasMore;\n }\n\n players = allPlayers;\n hasMore = false;\n }\n\n return {\n ...serverInfo,\n players,\n totalPlayers: playerList.totalPlayers,\n offset: playerList.offset,\n hasMore,\n };\n}\n","import type { Player } from './types.js';\n\n/**\n * V2 query type constants.\n */\nexport const V2QueryType = {\n CHALLENGE: 0x00,\n BASIC: 0x01,\n PLAYERS: 0x02,\n} as const;\n\n/**\n * V2 response flag constants.\n */\nexport const V2ResponseFlag = {\n HAS_MORE_PLAYERS: 0x0001,\n AUTH_REQUIRED: 0x0002,\n IS_NETWORK: 0x0010,\n HAS_ADDRESS: 0x0020,\n} as const;\n\n/**\n * V2 TLV type constants.\n */\nexport const V2TLVType = {\n SERVER_INFO: 0x0001,\n PLAYER_LIST: 0x0002,\n} as const;\n\n/**\n * Basic server information from V2 protocol.\n */\nexport interface ServerInfoV2 {\n /** Server display name */\n serverName: string;\n /** Message of the day */\n motd: string;\n /** Current number of players online */\n currentPlayers: number;\n /** Maximum player capacity */\n maxPlayers: number;\n /** Server version string */\n version: string;\n /** Protocol version number */\n protocolVersion: number;\n /** Protocol hash string */\n protocolHash: string;\n /** Server host (if FLAG_RESPONSE_HAS_ADDRESS) */\n host?: string;\n /** Server port (if FLAG_RESPONSE_HAS_ADDRESS) */\n hostPort?: number;\n /** Whether the server is in network aggregation mode */\n isNetwork: boolean;\n}\n\n/**\n * V2 server info combined with player list.\n */\nexport interface ServerInfoV2WithPlayers extends ServerInfoV2 {\n /** List of online players */\n players: Player[];\n /** Total players on server */\n totalPlayers: number;\n /** Offset used for this response */\n offset: number;\n /** Whether more players are available */\n hasMore: boolean;\n}\n\n/**\n * Options for V2 query function.\n */\nexport interface QueryV2Options {\n /** Timeout in milliseconds (default: 5000) */\n timeout?: number;\n /** Request player list: false=basic only, true=single page, 'all'=paginate all */\n players?: boolean | 'all';\n /** Starting offset for player pagination */\n playerOffset?: number;\n /** Optional auth token for private servers */\n authToken?: string;\n}\n","import type { Player } from './types.js';\nimport type { ServerInfoV2 } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport { BufferReader } from './protocol.js';\n\n// Magic bytes for V2 protocol\nexport const V2_REQUEST_MAGIC = Buffer.from('ONEQUERY', 'ascii');\nexport const V2_RESPONSE_MAGIC = Buffer.from('ONEREPLY', 'ascii');\n\n// Challenge token size\nexport const CHALLENGE_TOKEN_SIZE = 32;\n\n/**\n * Build a challenge request packet.\n * Format: Magic (8) + Type (1)\n */\nexport function buildChallengeRequest(): Buffer {\n const buf = Buffer.alloc(V2_REQUEST_MAGIC.length + 1);\n V2_REQUEST_MAGIC.copy(buf, 0);\n buf[V2_REQUEST_MAGIC.length] = V2QueryType.CHALLENGE;\n return buf;\n}\n\n/**\n * Build a V2 request packet.\n * Format: Magic (8) + Type (1) + Token (32) + RequestID (4) + Flags (2) + Offset (4) + [AuthToken]\n */\nexport function buildV2Request(\n type: number,\n token: Buffer,\n requestId: number,\n flags: number,\n offset: number,\n authToken?: string\n): Buffer {\n const authBuf = authToken ? Buffer.from(authToken, 'utf8') : null;\n const authLength = authBuf ? 2 + authBuf.length : 0;\n const totalLength = V2_REQUEST_MAGIC.length + 1 + 32 + 4 + 2 + 4 + authLength;\n\n const buf = Buffer.alloc(totalLength);\n let pos = 0;\n\n // Magic\n V2_REQUEST_MAGIC.copy(buf, pos);\n pos += V2_REQUEST_MAGIC.length;\n\n // Type\n buf[pos++] = type;\n\n // Token\n token.copy(buf, pos);\n pos += 32;\n\n // Request ID (LE)\n buf.writeUInt32LE(requestId, pos);\n pos += 4;\n\n // Flags (LE)\n buf.writeUInt16LE(flags, pos);\n pos += 2;\n\n // Offset (LE)\n buf.writeUInt32LE(offset, pos);\n pos += 4;\n\n // Auth token (optional)\n if (authBuf) {\n buf.writeUInt16LE(authBuf.length, pos);\n pos += 2;\n authBuf.copy(buf, pos);\n }\n\n return buf;\n}\n\n/**\n * Parsed TLV entry.\n */\nexport interface TLVEntry {\n type: number;\n value: Buffer;\n}\n\n/**\n * Parsed V2 response header.\n */\nexport interface V2ResponseHeader {\n protocolVersion: number;\n flags: number;\n requestId: number;\n payloadLength: number;\n}\n\n/**\n * Validate V2 response magic bytes.\n */\nexport function validateV2Response(buf: Buffer): boolean {\n if (buf.length < V2_RESPONSE_MAGIC.length) {\n return false;\n }\n return buf.subarray(0, V2_RESPONSE_MAGIC.length).equals(V2_RESPONSE_MAGIC);\n}\n\n/**\n * Parse challenge response to extract 32-byte token.\n * Format: Magic (8) + Status (1) + Token (32) + [padding]\n */\nexport function parseChallengeResponse(buf: Buffer): Buffer {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n // Challenge response: Magic (8) + Status (1) + Token (32)\n const tokenOffset = V2_RESPONSE_MAGIC.length + 1; // Skip magic and status byte\n if (buf.length < tokenOffset + CHALLENGE_TOKEN_SIZE) {\n throw new Error('Invalid challenge response: too short');\n }\n\n return Buffer.from(buf.subarray(tokenOffset, tokenOffset + CHALLENGE_TOKEN_SIZE));\n}\n\n/**\n * Parse V2 response header.\n */\nexport function parseV2ResponseHeader(buf: Buffer): V2ResponseHeader {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(V2_RESPONSE_MAGIC.length);\n\n const protocolVersion = reader.readUInt8();\n const flags = reader.readUInt16LE();\n const requestId = reader.readUInt32LE();\n const payloadLength = reader.readUInt16LE();\n\n return { protocolVersion, flags, requestId, payloadLength };\n}\n\n/**\n * Parse TLV entries from payload.\n */\nexport function parseTLVEntries(payload: Buffer): TLVEntry[] {\n const entries: TLVEntry[] = [];\n const reader = new BufferReader(payload);\n\n while (reader.remaining >= 4) {\n const type = reader.readUInt16LE();\n const length = reader.readUInt16LE();\n\n if (reader.remaining < length) {\n throw new Error('Invalid TLV: truncated value');\n }\n\n const value = reader.readBytes(length);\n entries.push({ type, value });\n }\n\n return entries;\n}\n\n/**\n * Parse full V2 response (header + TLV payload).\n */\nexport function parseV2Response(buf: Buffer): { header: V2ResponseHeader; entries: TLVEntry[] } {\n const header = parseV2ResponseHeader(buf);\n\n // Header is 17 bytes: Magic (8) + Version (1) + Flags (2) + RequestID (4) + PayloadLength (2)\n const headerSize = V2_RESPONSE_MAGIC.length + 1 + 2 + 4 + 2;\n const payload = buf.subarray(headerSize, headerSize + header.payloadLength);\n\n const entries = parseTLVEntries(payload);\n\n return { header, entries };\n}\n\n/**\n * Parse SERVER_INFO TLV into ServerInfoV2.\n * Note: isNetwork is determined from response flags, not from the TLV data.\n */\nexport function parseTLVServerInfo(entry: TLVEntry, hasAddress: boolean, isNetwork: boolean): ServerInfoV2 {\n if (entry.type !== V2TLVType.SERVER_INFO) {\n throw new Error(`Expected SERVER_INFO TLV (${V2TLVType.SERVER_INFO}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n const result: ServerInfoV2 = {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n version,\n protocolVersion,\n protocolHash,\n isNetwork,\n };\n\n if (hasAddress && reader.remaining >= 2) {\n result.host = reader.readString();\n if (reader.remaining >= 2) {\n result.hostPort = reader.readUInt16LE();\n }\n }\n\n return result;\n}\n\n/**\n * Parsed player list from TLV.\n */\nexport interface ParsedPlayerList {\n players: Player[];\n totalPlayers: number;\n offset: number;\n}\n\n/**\n * Parse PLAYER_LIST TLV.\n * Format: totalPlayers (4) + playersInResponse (4) + offset (4) + player entries\n */\nexport function parseTLVPlayerList(entry: TLVEntry): ParsedPlayerList {\n if (entry.type !== V2TLVType.PLAYER_LIST) {\n throw new Error(`Expected PLAYER_LIST TLV (${V2TLVType.PLAYER_LIST}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const totalPlayers = reader.readInt32LE();\n const playersInResponse = reader.readInt32LE();\n const offset = reader.readInt32LE();\n\n const players: Player[] = [];\n for (let i = 0; i < playersInResponse; i++) {\n const name = reader.readString();\n const uuid = reader.readUUIDLE(); // UUIDs are little-endian in V2\n players.push({ name, uuid });\n }\n\n return { players, totalPlayers, offset };\n}\n\n/**\n * Find a TLV entry by type.\n */\nexport function findTLVEntry(entries: TLVEntry[], type: number): TLVEntry | undefined {\n return entries.find((e) => e.type === type);\n}\n"],"mappings":"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","names":["dgram","DEFAULT_TIMEOUT","dgram"]}
|
|
1
|
+
{"version":3,"sources":["../src/query.ts","../src/protocol.ts","../src/query-v2.ts","../src/types-v2.ts","../src/protocol-v2.ts"],"sourcesContent":["import dgram from 'node:dgram';\nimport {\n buildRequest,\n parseBasicResponse,\n parseFullResponse,\n TYPE_BASIC,\n TYPE_FULL,\n} from './protocol.js';\nimport type { QueryOptions, ServerInfo, ServerInfoFull } from './types.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n/**\n * Send a UDP query and wait for response.\n */\nfunction sendQuery(\n host: string,\n port: number,\n type: number,\n timeout: number\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n const request = buildRequest(type);\n socket.send(request, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Query a Hytale server for information.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (full if options.full is true)\n *\n * @example\n * ```typescript\n * // Basic query\n * const info = await query('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // Full query (includes players + plugins)\n * const full = await query('play.example.com', 5520, { full: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function query(\n host: string,\n port?: number,\n options?: QueryOptions & { full?: false }\n): Promise<ServerInfo>;\nexport async function query(\n host: string,\n port: number,\n options: QueryOptions & { full: true }\n): Promise<ServerInfoFull>;\nexport async function query(\n host: string,\n port = 5520,\n options: QueryOptions & { full?: boolean } = {}\n): Promise<ServerInfo | ServerInfoFull> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const type = options.full ? TYPE_FULL : TYPE_BASIC;\n const response = await sendQuery(host, port, type, timeout);\n\n if (options.full) {\n return parseFullResponse(response);\n }\n return parseBasicResponse(response);\n}\n","import type { Player, Plugin, ServerInfo, ServerInfoFull } from './types.js';\n\n// Protocol constants\nexport const REQUEST_MAGIC = Buffer.from('HYQUERY\\0', 'ascii');\nexport const RESPONSE_MAGIC = Buffer.from('HYREPLY\\0', 'ascii');\nexport const TYPE_BASIC = 0x00;\nexport const TYPE_FULL = 0x01;\n\n// Capability flags (V1 extension)\nexport const CAP_V2_PROTOCOL = 0x01;\nexport const CAP_NETWORK_MODE = 0x02;\n\n/**\n * Build a query request packet.\n */\nexport function buildRequest(type: number): Buffer {\n const buf = Buffer.alloc(REQUEST_MAGIC.length + 1);\n REQUEST_MAGIC.copy(buf, 0);\n buf[REQUEST_MAGIC.length] = type;\n return buf;\n}\n\n/**\n * Buffer reader helper for parsing responses.\n */\nexport class BufferReader {\n private offset = 0;\n\n constructor(private buf: Buffer) {}\n\n private checkBounds(needed: number): void {\n if (this.offset + needed > this.buf.length) {\n throw new RangeError(\n `Buffer underflow: need ${needed} bytes at offset ${this.offset}, but buffer is ${this.buf.length} bytes`\n );\n }\n }\n\n readBytes(length: number): Buffer {\n this.checkBounds(length);\n const slice = this.buf.subarray(this.offset, this.offset + length);\n this.offset += length;\n return slice;\n }\n\n readUInt8(): number {\n this.checkBounds(1);\n return this.buf[this.offset++];\n }\n\n readUInt16LE(): number {\n this.checkBounds(2);\n const value = this.buf.readUInt16LE(this.offset);\n this.offset += 2;\n return value;\n }\n\n readUInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readUInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readInt32LE(): number {\n this.checkBounds(4);\n const value = this.buf.readInt32LE(this.offset);\n this.offset += 4;\n return value;\n }\n\n readBigInt64BE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64BE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readBoolean(): boolean {\n this.checkBounds(1);\n return this.buf[this.offset++] !== 0;\n }\n\n readString(): string {\n const length = this.readUInt16LE();\n if (length > this.remaining) {\n throw new RangeError(\n `Invalid string length ${length} at offset ${this.offset - 2}, only ${this.remaining} bytes remaining`\n );\n }\n const bytes = this.readBytes(length);\n return bytes.toString('utf8');\n }\n\n readUUID(): string {\n const msb = this.readBigInt64BE();\n const lsb = this.readBigInt64BE();\n return formatUUID(msb, lsb);\n }\n\n readBigInt64LE(): bigint {\n this.checkBounds(8);\n const value = this.buf.readBigInt64LE(this.offset);\n this.offset += 8;\n return value;\n }\n\n readUUIDLE(): string {\n const msb = this.readBigInt64LE();\n const lsb = this.readBigInt64LE();\n return formatUUID(msb, lsb);\n }\n\n get remaining(): number {\n return this.buf.length - this.offset;\n }\n\n get position(): number {\n return this.offset;\n }\n}\n\n/**\n * Format UUID from most/least significant bits.\n */\nfunction formatUUID(msb: bigint, lsb: bigint): string {\n const toHex = (n: bigint): string => {\n if (n < 0n) {\n n = BigInt.asUintN(64, n);\n }\n return n.toString(16).padStart(16, '0');\n };\n\n const hex = toHex(msb) + toHex(lsb);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Validate response magic bytes.\n */\nexport function validateResponse(buf: Buffer): boolean {\n if (buf.length < RESPONSE_MAGIC.length + 1) {\n return false;\n }\n return buf.subarray(0, RESPONSE_MAGIC.length).equals(RESPONSE_MAGIC);\n}\n\n/**\n * Parse capability extension from remaining bytes (3 bytes: 2 caps + 1 version).\n * Only parses if exactly 3 bytes remain to avoid misinterpreting other data.\n */\nfunction parseCapabilities(reader: BufferReader): {\n supportsV2: boolean;\n isNetworkMode: boolean;\n v2Version: number;\n} {\n if (reader.remaining === 3) {\n const caps = reader.readUInt16LE();\n const v2Version = reader.readUInt8();\n return {\n supportsV2: (caps & CAP_V2_PROTOCOL) !== 0,\n isNetworkMode: (caps & CAP_NETWORK_MODE) !== 0,\n v2Version,\n };\n }\n return { supportsV2: false, isNetworkMode: false, v2Version: 0 };\n}\n\n/**\n * Check if hostPort is encoded as int32 (legacy) vs uint16 (extended).\n * Returns true if int32, false if uint16.\n */\nfunction isLegacyPortFormat(buf: Buffer, offsetAfterMaxPlayers: number): boolean {\n const versionLenOffset = offsetAfterMaxPlayers + 2; // assuming uint16 port\n if (versionLenOffset + 2 > buf.length) return true;\n\n const versionLen = buf.readUInt16LE(versionLenOffset);\n // If version length is 0 or invalid, port is likely int32 (legacy format)\n return versionLen === 0 || versionLen > 500;\n}\n\n/**\n * Parse a basic query response.\n * Supports both extended format (uint16 port + protocolVersion/Hash) and legacy format (int32 port).\n */\nexport function parseBasicResponse(buf: Buffer): ServerInfo {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic + type\n reader.readBytes(RESPONSE_MAGIC.length + 1);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n\n // Detect format: legacy uses int32 for port, extended uses uint16\n const legacy = isLegacyPortFormat(buf, reader.position);\n\n const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = legacy ? 0 : reader.readInt32LE();\n const protocolHash = legacy ? '' : reader.readString();\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n ...capabilities,\n };\n}\n\n/**\n * Parse a full query response.\n * Supports both extended and legacy formats.\n */\nexport function parseFullResponse(buf: Buffer): ServerInfoFull {\n if (!validateResponse(buf)) {\n throw new Error('Invalid response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic + type\n reader.readBytes(RESPONSE_MAGIC.length + 1);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n\n // Detect format\n const legacy = isLegacyPortFormat(buf, reader.position);\n\n const hostPort = legacy ? reader.readInt32LE() : reader.readUInt16LE();\n const version = reader.readString();\n const protocolVersion = legacy ? 0 : reader.readInt32LE();\n const protocolHash = legacy ? '' : reader.readString();\n\n // Player list\n const playerCount = reader.readInt32LE();\n const players: Player[] = [];\n for (let i = 0; i < playerCount; i++) {\n players.push({\n name: reader.readString(),\n uuid: reader.readUUID(),\n });\n }\n\n // Plugin list\n const pluginCount = reader.readInt32LE();\n const plugins: Plugin[] = [];\n for (let i = 0; i < pluginCount; i++) {\n plugins.push({\n id: reader.readString(),\n version: reader.readString(),\n enabled: reader.readBoolean(),\n });\n }\n\n const capabilities = parseCapabilities(reader);\n\n return {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n hostPort,\n version,\n protocolVersion,\n protocolHash,\n players,\n plugins,\n ...capabilities,\n };\n}\n","import dgram from 'node:dgram';\nimport type { Player } from './types.js';\nimport type { QueryV2Options, ServerInfoV2, ServerInfoV2WithPlayers } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport {\n buildChallengeRequest,\n buildV2Request,\n findTLVEntry,\n parseChallengeResponse,\n parseTLVPlayerList,\n parseTLVServerInfo,\n parseV2Response,\n} from './protocol-v2.js';\n\nconst DEFAULT_TIMEOUT = 5000;\n\n// Challenge token cache configuration\nconst TOKEN_TTL_MS = 30_000;\nconst TOKEN_REFRESH_BUFFER_MS = 5_000;\nconst CLEANUP_INTERVAL_MS = 60_000;\n\ninterface CachedChallenge {\n token: Buffer;\n expiresAt: number;\n requestId: number;\n}\n\n// Module-level cache\nconst challengeCache = new Map<string, CachedChallenge>();\nlet cleanupScheduled = false;\n\nfunction scheduleCleanup(): void {\n if (cleanupScheduled) return;\n cleanupScheduled = true;\n\n setTimeout(() => {\n const now = Date.now();\n for (const [key, entry] of challengeCache) {\n if (now >= entry.expiresAt) {\n challengeCache.delete(key);\n }\n }\n cleanupScheduled = false;\n if (challengeCache.size > 0) {\n scheduleCleanup();\n }\n }, CLEANUP_INTERVAL_MS).unref();\n}\n\n/**\n * Clear the challenge token cache.\n * Useful for testing or forcing fresh challenges.\n */\nexport function clearChallengeCache(): void {\n challengeCache.clear();\n}\n\n/**\n * Send a UDP packet and wait for response.\n */\nfunction sendUDP(host: string, port: number, data: Buffer, timeout: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const socket = dgram.createSocket('udp4');\n let timeoutHandle: NodeJS.Timeout;\n let closed = false;\n\n const cleanup = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timeoutHandle);\n try {\n socket.close();\n } catch {\n // Already closed\n }\n };\n\n socket.on('message', (msg) => {\n cleanup();\n resolve(msg);\n });\n\n socket.on('error', (err) => {\n cleanup();\n reject(err);\n });\n\n timeoutHandle = setTimeout(() => {\n cleanup();\n reject(new Error(`Query timeout after ${timeout}ms`));\n }, timeout);\n\n socket.send(data, port, host, (err) => {\n if (err) {\n cleanup();\n reject(err);\n }\n });\n });\n}\n\n/**\n * Fetch a new challenge token from the server.\n */\nasync function fetchChallenge(host: string, port: number, timeout: number): Promise<Buffer> {\n const request = buildChallengeRequest();\n const response = await sendUDP(host, port, request, timeout);\n return parseChallengeResponse(response);\n}\n\n/**\n * Get or refresh a challenge token for a host:port.\n */\nasync function ensureChallenge(\n host: string,\n port: number,\n timeout: number\n): Promise<CachedChallenge> {\n const key = `${host}:${port}`;\n const cached = challengeCache.get(key);\n const now = Date.now();\n\n // Return cached if still valid (with buffer time)\n if (cached && now < cached.expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n cached.requestId++;\n return cached;\n }\n\n // Fetch new challenge token\n const token = await fetchChallenge(host, port, timeout);\n const entry: CachedChallenge = {\n token,\n expiresAt: now + TOKEN_TTL_MS,\n requestId: 1,\n };\n\n challengeCache.set(key, entry);\n scheduleCleanup();\n\n return entry;\n}\n\n/**\n * Perform a V2 query request.\n */\nasync function performV2Query(\n host: string,\n port: number,\n type: number,\n offset: number,\n timeout: number,\n authToken?: string\n): Promise<{ header: ReturnType<typeof parseV2Response>['header']; entries: ReturnType<typeof parseV2Response>['entries'] }> {\n const challenge = await ensureChallenge(host, port, timeout);\n\n const request = buildV2Request(type, challenge.token, challenge.requestId, 0, offset, authToken);\n const response = await sendUDP(host, port, request, timeout);\n\n return parseV2Response(response);\n}\n\n/**\n * Query a Hytale server using V2 protocol (basic info only).\n */\nexport async function queryV2(\n host: string,\n port?: number,\n options?: QueryV2Options & { players?: false }\n): Promise<ServerInfoV2>;\n\n/**\n * Query a Hytale server using V2 protocol with player list.\n */\nexport async function queryV2(\n host: string,\n port: number,\n options: QueryV2Options & { players: true | 'all' }\n): Promise<ServerInfoV2WithPlayers>;\n\n/**\n * Query a Hytale server using V2 protocol.\n *\n * @param host - Server hostname or IP address\n * @param port - Server port (default: 5520)\n * @param options - Query options\n * @returns Server information (with players if requested)\n *\n * @example\n * ```typescript\n * // Basic V2 query\n * const info = await queryV2('play.example.com', 5520);\n * console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);\n *\n * // V2 query with players\n * const full = await queryV2('play.example.com', 5520, { players: true });\n * console.log('Players:', full.players.map(p => p.name).join(', '));\n *\n * // V2 query with all players (auto-pagination)\n * const all = await queryV2('play.example.com', 5520, { players: 'all' });\n * console.log(`All ${all.totalPlayers} players:`, all.players.map(p => p.name).join(', '));\n * ```\n */\nexport async function queryV2(\n host: string,\n port = 5520,\n options: QueryV2Options = {}\n): Promise<ServerInfoV2 | ServerInfoV2WithPlayers> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const wantPlayers = options.players === true || options.players === 'all';\n const offset = options.playerOffset ?? 0;\n\n // First, always do a BASIC query to get server info\n const { header: basicHeader, entries: basicEntries } = await performV2Query(\n host,\n port,\n V2QueryType.BASIC,\n 0,\n timeout,\n options.authToken\n );\n\n const hasAddress = (basicHeader.flags & V2ResponseFlag.HAS_ADDRESS) !== 0;\n const isNetwork = (basicHeader.flags & V2ResponseFlag.IS_NETWORK) !== 0;\n\n // Parse server info\n const serverInfoEntry = findTLVEntry(basicEntries, V2TLVType.SERVER_INFO);\n if (!serverInfoEntry) {\n throw new Error('Response missing SERVER_INFO TLV');\n }\n const serverInfo = parseTLVServerInfo(serverInfoEntry, hasAddress, isNetwork);\n\n // If players not requested, return basic info\n if (!wantPlayers) {\n return serverInfo;\n }\n\n // Do a PLAYERS query to get player list\n const { header: playersHeader, entries: playersEntries } = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n offset,\n timeout,\n options.authToken\n );\n\n const hasMorePlayers = (playersHeader.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n\n // Parse player list\n const playerListEntry = findTLVEntry(playersEntries, V2TLVType.PLAYER_LIST);\n if (!playerListEntry) {\n throw new Error('Response missing PLAYER_LIST TLV');\n }\n const playerList = parseTLVPlayerList(playerListEntry);\n\n let players = playerList.players;\n let currentOffset = playerList.offset + players.length;\n let hasMore = hasMorePlayers;\n\n // If 'all' mode, paginate through remaining players\n if (options.players === 'all' && hasMore) {\n const allPlayers: Player[] = [...players];\n\n while (hasMore) {\n const nextResult = await performV2Query(\n host,\n port,\n V2QueryType.PLAYERS,\n currentOffset,\n timeout,\n options.authToken\n );\n\n const nextHasMore = (nextResult.header.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;\n const nextPlayerEntry = findTLVEntry(nextResult.entries, V2TLVType.PLAYER_LIST);\n\n if (!nextPlayerEntry) {\n break;\n }\n\n const nextPlayerList = parseTLVPlayerList(nextPlayerEntry);\n allPlayers.push(...nextPlayerList.players);\n currentOffset = nextPlayerList.offset + nextPlayerList.players.length;\n hasMore = nextHasMore;\n }\n\n players = allPlayers;\n hasMore = false;\n }\n\n return {\n ...serverInfo,\n players,\n totalPlayers: playerList.totalPlayers,\n offset: playerList.offset,\n hasMore,\n };\n}\n","import type { Player } from './types.js';\n\n/**\n * V2 query type constants.\n */\nexport const V2QueryType = {\n CHALLENGE: 0x00,\n BASIC: 0x01,\n PLAYERS: 0x02,\n} as const;\n\n/**\n * V2 response flag constants.\n */\nexport const V2ResponseFlag = {\n HAS_MORE_PLAYERS: 0x0001,\n AUTH_REQUIRED: 0x0002,\n IS_NETWORK: 0x0010,\n HAS_ADDRESS: 0x0020,\n} as const;\n\n/**\n * V2 TLV type constants.\n */\nexport const V2TLVType = {\n SERVER_INFO: 0x0001,\n PLAYER_LIST: 0x0002,\n} as const;\n\n/**\n * Basic server information from V2 protocol.\n */\nexport interface ServerInfoV2 {\n /** Server display name */\n serverName: string;\n /** Message of the day */\n motd: string;\n /** Current number of players online */\n currentPlayers: number;\n /** Maximum player capacity */\n maxPlayers: number;\n /** Server version string */\n version: string;\n /** Protocol version number */\n protocolVersion: number;\n /** Protocol hash string */\n protocolHash: string;\n /** Server host (if FLAG_RESPONSE_HAS_ADDRESS) */\n host?: string;\n /** Server port (if FLAG_RESPONSE_HAS_ADDRESS) */\n hostPort?: number;\n /** Whether the server is in network aggregation mode */\n isNetwork: boolean;\n}\n\n/**\n * V2 server info combined with player list.\n */\nexport interface ServerInfoV2WithPlayers extends ServerInfoV2 {\n /** List of online players */\n players: Player[];\n /** Total players on server */\n totalPlayers: number;\n /** Offset used for this response */\n offset: number;\n /** Whether more players are available */\n hasMore: boolean;\n}\n\n/**\n * Options for V2 query function.\n */\nexport interface QueryV2Options {\n /** Timeout in milliseconds (default: 5000) */\n timeout?: number;\n /** Request player list: false=basic only, true=single page, 'all'=paginate all */\n players?: boolean | 'all';\n /** Starting offset for player pagination */\n playerOffset?: number;\n /** Optional auth token for private servers */\n authToken?: string;\n}\n","import type { Player } from './types.js';\nimport type { ServerInfoV2 } from './types-v2.js';\nimport { V2QueryType, V2ResponseFlag, V2TLVType } from './types-v2.js';\nimport { BufferReader } from './protocol.js';\n\n// Magic bytes for V2 protocol\nexport const V2_REQUEST_MAGIC = Buffer.from('ONEQUERY', 'ascii');\nexport const V2_RESPONSE_MAGIC = Buffer.from('ONEREPLY', 'ascii');\n\n// Challenge token size\nexport const CHALLENGE_TOKEN_SIZE = 32;\n\n/**\n * Build a challenge request packet.\n * Format: Magic (8) + Type (1)\n */\nexport function buildChallengeRequest(): Buffer {\n const buf = Buffer.alloc(V2_REQUEST_MAGIC.length + 1);\n V2_REQUEST_MAGIC.copy(buf, 0);\n buf[V2_REQUEST_MAGIC.length] = V2QueryType.CHALLENGE;\n return buf;\n}\n\n/**\n * Build a V2 request packet.\n * Format: Magic (8) + Type (1) + Token (32) + RequestID (4) + Flags (2) + Offset (4) + [AuthToken]\n */\nexport function buildV2Request(\n type: number,\n token: Buffer,\n requestId: number,\n flags: number,\n offset: number,\n authToken?: string\n): Buffer {\n const authBuf = authToken ? Buffer.from(authToken, 'utf8') : null;\n const authLength = authBuf ? 2 + authBuf.length : 0;\n const totalLength = V2_REQUEST_MAGIC.length + 1 + 32 + 4 + 2 + 4 + authLength;\n\n const buf = Buffer.alloc(totalLength);\n let pos = 0;\n\n // Magic\n V2_REQUEST_MAGIC.copy(buf, pos);\n pos += V2_REQUEST_MAGIC.length;\n\n // Type\n buf[pos++] = type;\n\n // Token\n token.copy(buf, pos);\n pos += 32;\n\n // Request ID (LE)\n buf.writeUInt32LE(requestId, pos);\n pos += 4;\n\n // Flags (LE)\n buf.writeUInt16LE(flags, pos);\n pos += 2;\n\n // Offset (LE)\n buf.writeUInt32LE(offset, pos);\n pos += 4;\n\n // Auth token (optional)\n if (authBuf) {\n buf.writeUInt16LE(authBuf.length, pos);\n pos += 2;\n authBuf.copy(buf, pos);\n }\n\n return buf;\n}\n\n/**\n * Parsed TLV entry.\n */\nexport interface TLVEntry {\n type: number;\n value: Buffer;\n}\n\n/**\n * Parsed V2 response header.\n */\nexport interface V2ResponseHeader {\n protocolVersion: number;\n flags: number;\n requestId: number;\n payloadLength: number;\n}\n\n/**\n * Validate V2 response magic bytes.\n */\nexport function validateV2Response(buf: Buffer): boolean {\n if (buf.length < V2_RESPONSE_MAGIC.length) {\n return false;\n }\n return buf.subarray(0, V2_RESPONSE_MAGIC.length).equals(V2_RESPONSE_MAGIC);\n}\n\n/**\n * Parse challenge response to extract 32-byte token.\n * Format: Magic (8) + Status (1) + Token (32) + [padding]\n */\nexport function parseChallengeResponse(buf: Buffer): Buffer {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n // Challenge response: Magic (8) + Status (1) + Token (32)\n const tokenOffset = V2_RESPONSE_MAGIC.length + 1; // Skip magic and status byte\n if (buf.length < tokenOffset + CHALLENGE_TOKEN_SIZE) {\n throw new Error('Invalid challenge response: too short');\n }\n\n return Buffer.from(buf.subarray(tokenOffset, tokenOffset + CHALLENGE_TOKEN_SIZE));\n}\n\n/**\n * Parse V2 response header.\n */\nexport function parseV2ResponseHeader(buf: Buffer): V2ResponseHeader {\n if (!validateV2Response(buf)) {\n throw new Error('Invalid V2 response: magic mismatch');\n }\n\n const reader = new BufferReader(buf);\n\n // Skip magic\n reader.readBytes(V2_RESPONSE_MAGIC.length);\n\n const protocolVersion = reader.readUInt8();\n const flags = reader.readUInt16LE();\n const requestId = reader.readUInt32LE();\n const payloadLength = reader.readUInt16LE();\n\n return { protocolVersion, flags, requestId, payloadLength };\n}\n\n/**\n * Parse TLV entries from payload.\n */\nexport function parseTLVEntries(payload: Buffer): TLVEntry[] {\n const entries: TLVEntry[] = [];\n const reader = new BufferReader(payload);\n\n while (reader.remaining >= 4) {\n const type = reader.readUInt16LE();\n const length = reader.readUInt16LE();\n\n if (reader.remaining < length) {\n throw new Error('Invalid TLV: truncated value');\n }\n\n const value = reader.readBytes(length);\n entries.push({ type, value });\n }\n\n return entries;\n}\n\n/**\n * Parse full V2 response (header + TLV payload).\n */\nexport function parseV2Response(buf: Buffer): { header: V2ResponseHeader; entries: TLVEntry[] } {\n const header = parseV2ResponseHeader(buf);\n\n // Header is 17 bytes: Magic (8) + Version (1) + Flags (2) + RequestID (4) + PayloadLength (2)\n const headerSize = V2_RESPONSE_MAGIC.length + 1 + 2 + 4 + 2;\n const payload = buf.subarray(headerSize, headerSize + header.payloadLength);\n\n const entries = parseTLVEntries(payload);\n\n return { header, entries };\n}\n\n/**\n * Parse SERVER_INFO TLV into ServerInfoV2.\n * Note: isNetwork is determined from response flags, not from the TLV data.\n */\nexport function parseTLVServerInfo(entry: TLVEntry, hasAddress: boolean, isNetwork: boolean): ServerInfoV2 {\n if (entry.type !== V2TLVType.SERVER_INFO) {\n throw new Error(`Expected SERVER_INFO TLV (${V2TLVType.SERVER_INFO}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const serverName = reader.readString();\n const motd = reader.readString();\n const currentPlayers = reader.readInt32LE();\n const maxPlayers = reader.readInt32LE();\n const version = reader.readString();\n const protocolVersion = reader.readInt32LE();\n const protocolHash = reader.readString();\n\n const result: ServerInfoV2 = {\n serverName,\n motd,\n currentPlayers,\n maxPlayers,\n version,\n protocolVersion,\n protocolHash,\n isNetwork,\n };\n\n if (hasAddress && reader.remaining >= 2) {\n result.host = reader.readString();\n if (reader.remaining >= 2) {\n result.hostPort = reader.readUInt16LE();\n }\n }\n\n return result;\n}\n\n/**\n * Parsed player list from TLV.\n */\nexport interface ParsedPlayerList {\n players: Player[];\n totalPlayers: number;\n offset: number;\n}\n\n/**\n * Parse PLAYER_LIST TLV.\n * Format: totalPlayers (4) + playersInResponse (4) + offset (4) + player entries\n */\nexport function parseTLVPlayerList(entry: TLVEntry): ParsedPlayerList {\n if (entry.type !== V2TLVType.PLAYER_LIST) {\n throw new Error(`Expected PLAYER_LIST TLV (${V2TLVType.PLAYER_LIST}), got ${entry.type}`);\n }\n\n const reader = new BufferReader(entry.value);\n\n const totalPlayers = reader.readInt32LE();\n const playersInResponse = reader.readInt32LE();\n const offset = reader.readInt32LE();\n\n const players: Player[] = [];\n for (let i = 0; i < playersInResponse; i++) {\n const name = reader.readString();\n const uuid = reader.readUUIDLE(); // UUIDs are little-endian in V2\n players.push({ name, uuid });\n }\n\n return { players, totalPlayers, offset };\n}\n\n/**\n * Find a TLV entry by type.\n */\nexport function findTLVEntry(entries: TLVEntry[], type: number): TLVEntry | undefined {\n return entries.find((e) => e.type === type);\n}\n"],"mappings":"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","names":["dgram","DEFAULT_TIMEOUT","dgram"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hytaleone/query",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "TypeScript/JavaScript client for querying Hytale servers. Get server status, player count, player list, and plugin information via UDP protocol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|