CodeLogic.GameNetQuery
5.0.121
dotnet add package CodeLogic.GameNetQuery --version 5.0.121
NuGet\Install-Package CodeLogic.GameNetQuery -Version 5.0.121
<PackageReference Include="CodeLogic.GameNetQuery" Version="5.0.121" />
<PackageVersion Include="CodeLogic.GameNetQuery" Version="5.0.121" />
<PackageReference Include="CodeLogic.GameNetQuery" />
paket add CodeLogic.GameNetQuery --version 5.0.121
#r "nuget: CodeLogic.GameNetQuery, 5.0.121"
#:package CodeLogic.GameNetQuery@5.0.121
#addin nuget:?package=CodeLogic.GameNetQuery&version=5.0.121
#tool nuget:?package=CodeLogic.GameNetQuery&version=5.0.121
CodeLogic.GameNetQuery
Query and administer game servers from CodeLogic 5 — Valve A2S over UDP, Source RCON, and Minecraft GameSpy4 / RCON.
Speaks the wire protocols used by Source-engine and Minecraft servers: A2S over UDP for live server info and player lists, the Source RCON TCP protocol for remote console commands (with high-level Counter-Strike 2 / Counter-Strike: Source admin wrappers), and Minecraft's GameSpy4 UDP query plus RCON. No external NuGet dependencies.
Install
dotnet add package CodeLogic.GameNetQuery
Quick start
The query clients are used directly by namespace — they don't need a library instance. Load the library only when you want its lifecycle and health-check integration.
using CL.GameNetQuery.Valve;
using CL.GameNetQuery.Models;
using CodeLogic.Core.Results;
// A2S UDP — info + players + timing as a CodeLogic Result (no library load required)
Result<ServerQuery> query = await ValveUdpQuery.QueryAsync("203.0.113.10", 27015);
Console.WriteLine(query switch
{
ServerQuery q => $"{q.Info.Hostname}: {q.Players.Count} players in {q.DurationMs} ms",
Error e => $"{e.Code}: {e.Message}", // e.g. gamenetquery.timeout
null => "No result"
});
// Or the simple helpers: null / empty list on failure
ServerInfo? info = await ValveUdpQuery.GetServerInfoAsync("203.0.113.10", 27015);
if (info is { IsOnline: true })
Console.WriteLine($"{info.Hostname} — {info.Map} ({info.PlayerCount}/{info.MaxPlayers})");
List<PlayerInfo> players = await ValveUdpQuery.GetPlayerListAsync("203.0.113.10", 27015);
foreach (var p in players)
Console.WriteLine($"{p.Name} score {p.Score} {p.Ping}ms");
// Source RCON — remote console command
using var rcon = new ValveRconClient("203.0.113.10", 27015, "rcon-password");
if (await rcon.ConnectAsync())
Console.WriteLine(await rcon.SendCommandAsync("status"));
To wire the library into CodeLogic's lifecycle (so it participates in health checks):
using CL.GameNetQuery;
await Libraries.LoadAsync<GameNetQueryLibrary>(); // register before ConfigureAsync()
await CodeLogic.ConfigureAsync();
await CodeLogic.StartAsync();
Features
- Valve A2S UDP query —
ValveUdpQuery.QueryAsyncreturnsResult<ServerQuery>(info, players, duration) with typed failures (TimeoutError,UnavailableError, ...);GetServerInfoAsync/GetPlayerListAsyncreturnServerInfo?andList<PlayerInfo>, withnull/ an empty list on failure. - Source RCON —
ValveRconClientconnects over TCP and runs console commands verbatim, returning the raw server response string. Never build a raw command from untrusted text. - Counter-Strike admin wrappers —
CounterStrike2andCounterStrikeSourceexpose named helpers (change map, kick/ban by user id or name, say, cvars, restart…) over RCON; CS2 adds cheats, friendly-fire, team-balance, and slay. Arguments are validated before sending, so a player name likex; rcon_password pwnedis refused with aValidationError(gamenetquery.invalid_rcon_argument) instead of running a second command. statusparsers —ValveStatusParserandValveStatusParserCS2turn rawstatusoutput intoServerInfo/PlayerInfo.- Minecraft —
MinecraftQueryClient(GameSpy4 full-stat UDP query:hostname,map,numplayers,players, ... viaGetStatusValue) andMinecraftRconClient(RCON).
Configuration
None. CL.GameNetQuery is a zero-config library — it writes no config file. Every connection parameter (IP, port, RCON password, timeout) is passed directly to the query clients at call time.
Documentation
Full guide: CL.GameNetQuery documentation
Requirements
- CodeLogic 5.0+ · .NET 11
- No external NuGet dependencies.
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0 is compatible. |
-
net11.0
- CodeLogic (>= 5.0.37 && < 6.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.121 | 34 | 11/10/2026 |
| 5.0.118-preview | 33 | 11/8/2026 |
| 5.0.117-preview | 33 | 11/7/2026 |
| 5.0.116-preview | 41 | 11/1/2026 |
| 5.0.115-preview | 38 | 11/1/2026 |
| 5.0.113-preview | 44 | 10/31/2026 |
| 4.9.98 | 47 | 10/25/2026 |
| 4.8.93 | 73 | 9/23/2026 |
| 4.8.87 | 63 | 9/13/2026 |
| 4.8.85 | 57 | 9/12/2026 |
| 4.6.72 | 91 | 6/20/2026 |
| 4.6.69-preview | 61 | 6/20/2026 |
| 4.5.2 | 147 | 5/24/2026 |
| 4.5.2-preview.68 | 91 | 6/20/2026 |
| 4.5.1 | 126 | 5/24/2026 |
| 4.5.1-preview.56 | 82 | 5/24/2026 |
| 4.4.2-preview.53 | 83 | 5/24/2026 |
| 4.4.1 | 132 | 5/24/2026 |
| 4.0.5 | 128 | 5/15/2026 |
| 4.0.4 | 136 | 5/9/2026 |
# CL.GameNetQuery — Changelog
All notable changes to **CodeLogic.GameNetQuery** are documented here. Versions follow
[Semantic Versioning](https://semver.org/).
## 5.0 (2026-11-10)
### Breaking
- **Library ID is `CL.GameNetQuery`** (was `cl.gamenetquery`), like every other library. Its folder is `Libraries/CL.GameNetQuery`; CodeLogic 5 renames an existing `CL.gamenetquery` folder on case-sensitive file systems.
- Targets **.NET 11** (`net11.0`) and requires **CodeLogic 5.0** (`[5.0.0, 6.0.0)`). On .NET 10, stay on
4.9.x. CodeLogic 5 makes `Result<T>` / `Result` C# 15 unions and `Error` a closed record with
kind subtypes (`NotFoundError`, `ValidationError`, ...).
- **`QueryResult` removed.** The class (`Success`, `Info?`, `Players`, `Error` string, `DurationMs`,
`Ok`/`Fail`) is replaced by CodeLogic `Result<ServerQuery>`, where
`ServerQuery(ServerInfo Info, IReadOnlyList<PlayerInfo> Players, long DurationMs)` is the success value
and failures are `Error` kinds with `gamenetquery.*` codes.
- **Counter-Strike commands that take caller text return `Task<Result<string>>`** (was `Task<string>`), so an
unsafe argument can be refused: `ChangeMapAsync`, `KickPlayerAsync`, `BanPlayerAsync`, `UnbanPlayerAsync`,
`SetHostnameAsync`, `ExecConfigAsync`, `SetCvarAsync`, `GetCvarAsync`, `SayAsync` (both `CounterStrike2` and
`CounterStrikeSource`) and `CounterStrike2.SlayPlayerAsync`. Read the response from `.Value` after checking
`IsFailure`. Commands without a text argument (`RestartAsync`, `EnableCheatsAsync`, `SetFriendlyFireAsync(bool)`, ...) still return `Task<string>`.
- `BanPlayerAsync(string)` / `UnbanPlayerAsync(string)` take a user id or SteamID (`123`, `STEAM_1:0:123`,
`[U:1:123]`, `7656119...`); `banid` / `removeid` never accepted names, and a name is now a validation error.
The parameter is renamed `playerId`.
- `SetCvarAsync` quotes the value (`mp_roundtime "1.92"`), so values with spaces are sent as one argument.
### Security
- **RCON command injection in the Counter-Strike wrappers.** Caller values were spliced into the console line
unchecked, so kicking a player named `x; rcon_password pwned` (names come straight from `GetStatusAsync`) ran a
second command with full RCON rights. Every argument is now checked before anything is sent: free text
(names, messages, hostnames, cvar values, kick reasons) may not contain `;`, `"`, CR, LF, NUL or any other
control character; maps and configs must be relative paths of letters, digits and `_ - . /` without `..`;
cvar names and ids must match their shape. A rejected value is a `ValidationError`
`gamenetquery.invalid_rcon_argument`. `ValveRconClient.SendCommandAsync` / `MinecraftRconClient.SendCommandAsync`
stay raw: never pass untrusted text to them.
- **A2S replies are bound to the queried server.** `ValveUdpQuery` connects its UDP socket to the server and
discards datagrams from any other endpoint, so a third party can no longer answer a query in its place.
`MinecraftQueryClient` also checks the reply's source and session id.
### Added
- `KickPlayerAsync(int userId, string? reason = null)` (`kickid`) and `BanPlayerAsync(int userId, int minutes = 0)`
(`banid`) on both Counter-Strike wrappers: act on the user id, which hostile names cannot affect.
- `PlayerInfo.UserId` (`int?`): the user id from RCON `status` (`ValveStatusParser`, `ValveStatusParserCS2`,
`GetStatusAsync`); `null` from A2S.
- `GameNetQueryErrorCodes`: every `gamenetquery.*` code in one place.
- `ValveUdpQuery.QueryAsync(ip, port, includePlayers = true, timeoutMs = 3000, cancellationToken)` →
`Task<Result<ServerQuery>>`: A2S_INFO plus (optionally) A2S_PLAYER with the elapsed time. Failures:
`ValidationError` `gamenetquery.invalid_address` / `gamenetquery.invalid_timeout`, `TimeoutError`
`gamenetquery.timeout`, `UnavailableError` `gamenetquery.unreachable`, `InternalError`
`gamenetquery.malformed_response` / `gamenetquery.query_failed`.
### Fixed
- A2S_INFO answers the server's `S2C_CHALLENGE` (sent by Source servers since 2020) instead of parsing
the challenge packet as server info, and replies are checked for the expected A2S header. Applies to
`GetServerInfoAsync` / `GetPlayerListAsync` too (a wrong packet now gives `null` / empty, not garbage).
The UDP socket now matches the address family, so IPv6 addresses work.
- **`ValveRconClient.ConnectAsync` reported success for a wrong password.** It accepted the empty
`SERVERDATA_RESPONSE_VALUE` that Source servers send before the real answer. It now waits for the
`SERVERDATA_AUTH_RESPONSE`, requires the request id, and returns `false` for id `-1` (rejected password)
or when no auth response arrives within `ResponseTimeout`.
- **RCON responses split across TCP reads were dropped.** Both `ValveRconClient` and `MinecraftRconClient`
now frame packets exactly from a persistent buffer, so a packet split over several segments (or several
packets in one segment) is reassembled, and a malformed size field closes the connection instead of
desynchronising it.
- **Multi-packet responses are complete.** `ValveRconClient.SendCommandAsync` returned only the first
response packet (about 4 KiB, which truncated `status`/`cvarlist` on busy servers). Both clients now send
an end-of-response marker after each command (Valve: an empty `SERVERDATA_RESPONSE_VALUE`; Minecraft: a
packet of unknown type 200, answered with "Unknown request") and return every body up to its reply,
joined in order. Servers that don't answer the marker fall back to `MultiPacketIdleTimeout`.
- Each request uses a fresh id, so a late reply to a timed-out command is skipped instead of being
returned as the answer to the next one. Calls on one client are serialised. Request bodies and responses
are UTF-8 (Minecraft used ASCII).
- **`MinecraftQueryClient.QueryServer` never returned the documented keys.** It sent a basic-stat request
(without the four padding bytes) but parsed the reply as a full stat, so `hostname`, `map`, `numplayers` and
the rest never appeared. It now sends a full-stat request and parses the reply by its layout; the response
also has a `players` line (names joined by `", "`), empty values are empty (were `(empty)`), text is decoded
as UTF-8, and `QueryServer(string, ...)` returns an empty string for a non-IP address instead of throwing.
- `ServerInfo.Version` and `ServerInfo.Tags` are filled from A2S_INFO (version string and `sv_tags` keywords),
and `CounterStrike2.GetStatusAsync` fills `Version`. `RawResponse` is the raw `status` text for RCON-parsed
info and empty for A2S.
- A2S strings without a terminator, or starting past the end of the packet, are a
`gamenetquery.malformed_response` instead of reading out of bounds; a short A2S_PLAYER challenge is too.
### Added
- `ValveRconClient` / `MinecraftRconClient`: `ResponseTimeout` (default 5 s, was a fixed poll of about
5 s) and `MultiPacketIdleTimeout` (default 500 ms).
### Documentation
- The `QueryAsync` examples in the README and the guide switch on the result union
(`ServerQuery q => …, TimeoutError => …, Error e => …`). The guide's `ValveRconClient` member table no longer
breaks before `ResponseTimeout`, and `ValveStatusParserCS2.GetPlayerCount` shows its named tuple.
## 2026-10-24
### Changed
- Requires **CodeLogic 4.9+**. The official libraries move to the 4.9 line together with the framework,
whose live config changes the stateful libraries now use. No API changes in this library.
## 2026-09-12
### Changed
- Unified the version line with the CodeLogic framework on **4.8.x**. Every official
library and the framework now share one `major.minor`, so a given `4.8.<patch>`
means the same generation across all packages.
- `version.txt` moved from `4.6` to `4.8`. The patch component remains the CI run
number, composed at pack time; `AssemblyVersion` stays pinned at `Major.Minor.0.0`
(now `4.8.0.0`) so every patch in the line loads interchangeably.
## 2026-06-20
### Documentation
- Full rewrite of the README and the `docs/libs/gamenetquery.md` deep-dive to the current
CodeLogic house style.
- Clarified that the library is zero-config and that the query clients (`ValveUdpQuery`,
`ValveRconClient`, `CounterStrike2`/`CounterStrikeSource`, the status parsers, and the
Minecraft clients) are used directly by namespace — the `GameNetQueryLibrary` instance is
only needed for lifecycle/health integration.
- Documented that the query clients return model types directly (`ServerInfo?`,
`List<PlayerInfo>`, response strings) rather than the framework `Result`, including the
no-throw failure contracts.
- Added field tables for `ServerInfo` and `PlayerInfo`, method tables for both Counter-Strike
wrappers (shared set vs. CS2-only extras), and the `ValveStatusParser` /
`ValveStatusParserCS2` parser surfaces.
- No functional code changes.
## [4.5.2] — 2026-06-20
### Documentation
- Documented the `CounterStrike2` / `CounterStrikeSource` admin wrappers, including the
CS2-only helpers (`UnbanPlayerAsync`, `SetHostnameAsync`, `ExecConfigAsync`, `GetCvarAsync`,
`EnableCheatsAsync`/`DisableCheatsAsync`, `SetFriendlyFireAsync`, `SetTeamBalanceAsync`,
`SlayPlayerAsync`, `GetStatusRawAsync`) and the shared command subset.
- Documented the `status` parsers — `ValveStatusParser` (`GetServerAddress`, `GetVersion`,
`GetTags`, `GetPlayerList`, `ParseStatus`/`ParseStatusWithPlayers`) and `ValveStatusParserCS2`
(`GetPlayerList`, `GetSpawngroups`).
- Documented the `ValveRconClient(IPAddress, …)` overload, the `ushort` port and `timeoutMs`
parameters, and the no-throw failure contracts of the UDP/RCON clients.
- Documented the full `ServerInfo`, `PlayerInfo`, and `QueryResult` shapes (with `Ok`/`Fail`).
- Corrected the docs to match the API: `ServerInfo.Hostname` (not `Name`); `QueryResult.DurationMs`
(not `LatencyMs`) with non-null `Players`; and `MinecraftQueryClient.QueryServer` is synchronous
and returns key/value text read via `GetStatusValue` (not an awaited `QueryResult`).
- No functional code changes.
## [4.5.0] — 2026-05-24
### Changed
- **Unified versioning.** All CodeLogic.Libs now share a single version line
controlled by `version.txt` in the repo root. This is a version alignment
release — no functional changes to this library.
## [4.0.4] — 2026-04-16
### Changed
- README + manifest refresh for the v4 baseline. No functional changes vs 4.0.3.
- `LibraryManifest.Version` now reads from assembly metadata.
## [4.0.2] — 2026-04-09
### Changed
- Aligned with the v4 baseline across all libraries.
## [4.0.0] — 2026-04-09
Major rewrite. Republished as v4.0.0 to reset the version line under the
unified v4 baseline. Valve A2S + Minecraft Server List Ping queries with
typed request/response models and full XML documentation.
### Notes
- Earlier history is retained in the
[git log](https://github.com/zyntal-com/CodeLogic.Libs/commits/main/CL.GameNetQuery).