Blaster.NET is a .NET 10 port of AlliedModders Blaster for querying Valve game servers and collecting aggregated stats.
It includes:
- Blaster.Valve: Valve master/server query protocol support (SteamKit2 + A2S)
- Blaster.Batch: concurrent worker/batch processing
- Blaster.CLI: command-line server query tool (JSON output)
- Blaster.AmStats: stats collector that writes aggregates to an existing MySQL database
- Blaster.Tests: unit and integration tests
- Ported from Go to C#,
- Switched from old master server to GMS server query with SteamKit
- Added support for more games / mods
- Updated CS:GO to its new app id and re-enabled it for querying
- Filtered out SDR "fakeip" servers from querying by default; opt-in querying now supported via
--include-fakeip - Filtered redirect/spam "server farms" out of master results (configurable via
--max-servers-per-host) - Added
valve_game_idandlast_seencolumns to AmStats schema
- .NET SDK 10
- Network access to Steam/Valve query endpoints
- For
Blaster.AmStats: reachable MySQL database with the expected schema already created
dotnet build
dotnet testRun only integration tests:
dotnet test --filter "Category=Integration"See TESTING.md for running unit vs integration tests and the required environment variables.
Build optimized, self-contained Linux executables (no .NET runtime required):
# AmStats with trimming and ReadyToRun optimization
dotnet publish src/Blaster.AmStats -c Release -r linux-x64 --self-contained \
-p:PublishSingleFile=true -p:PublishReadyToRun=true \
-p:DebugType=none -p:DebugSymbols=false
# CLI with trimming and ReadyToRun optimization
dotnet publish src/Blaster.CLI -c Release -r linux-x64 --self-contained \
-p:PublishSingleFile=true -p:PublishReadyToRun=true \
-p:DebugType=none -p:DebugSymbols=falseFor Steam-backed integration tests, set credentials via environment variables first:
set BLASTER_TEST_STEAM_USERNAME=your_steam_username
set BLASTER_TEST_STEAM_PASSWORD=your_steam_password
dotnet test --filter "Category=Integration"Run help:
dotnet run --project src/Blaster.CLI -- --helpBasic query:
dotnet run --project src/Blaster.CLI -- --appids 240 --steam-username myuser --steam-password mypassMultiple app IDs:
dotnet run --project src/Blaster.CLI -- --appids 240 440 730Output modes:
dotnet run --project src/Blaster.CLI -- --appids 240 --format list
dotnet run --project src/Blaster.CLI -- --appids 240 --format map
dotnet run --project src/Blaster.CLI -- --appids 240 --format linesPerformance/behavior flags:
dotnet run --project src/Blaster.CLI -- --appids 240 --concurrency 20
dotnet run --project src/Blaster.CLI -- --appids 240 --no-info
dotnet run --project src/Blaster.CLI -- --appids 240 --no-rulesUsing environment variables instead of CLI secrets:
set BLASTER_STEAM_USERNAME=myuser
set BLASTER_STEAM_PASSWORD=mypass
dotnet run --project src/Blaster.CLI -- --appids 240 --format listUsing the Steam Web API transport:
dotnet run --project src/Blaster.CLI -- --appids 240 --transport web-api --steam-webapi-key YOURKEYHERE
# or via environment variable:
set BLASTER_STEAM_WEBAPI_KEY=YOURKEYHERE
dotnet run --project src/Blaster.CLI -- --appids 240 --transport web-api| Option | Description |
|---|---|
--appids <IDS...> |
Required. One or more Valve app IDs |
--format <list|map|lines> |
Output format (default: list) |
--transport <steam|web-api> |
Master-server transport (default: steam); see Master server transports |
--steam-username <U> |
Steam username (or BLASTER_STEAM_USERNAME); required for steam transport |
--steam-password <P> |
Steam password (or BLASTER_STEAM_PASSWORD); required for steam transport |
--steam-webapi-key <K> |
Steam Web API key (or BLASTER_STEAM_WEBAPI_KEY); required for web-api transport |
--log-level <LEVEL> |
Log level: trace, debug, info, warn, error, critical (default: info); trace surfaces detailed per-app fan-out query statistics |
--no-info |
Skip A2S_INFO queries |
--no-rules |
Skip A2S_RULES queries |
--include-fakeip |
Opt-in: also query SDR/fake-IP (169.254.*) servers via QueryByFakeIP instead of dropping them; see Fake-IP / SDR servers |
--concurrency <N> |
Max concurrent server queries (default: 20) |
--max-servers-per-host <N> |
Drop IPs advertising more than N servers as redirect/spam farms (or BLASTER_MAX_SERVERS_PER_HOST; default: 100; 0 disables); see Spam-farm filtering |
--help |
Show help |
Run help:
dotnet run --project src/Blaster.AmStats -- --helpRun collection:
dotnet run --project src/Blaster.AmStats -- --game hl1 --config config.yml --steam-username myuser --steam-password mypass
dotnet run --project src/Blaster.AmStats -- --game hl2 --config config.ymlEnable debug logging:
dotnet run --project src/Blaster.AmStats -- --game hl1 --config config.yml --log-level debug| Option | Description |
|---|---|
--game <hl1|hl2> |
Required. Game to collect stats for |
--config <PATH> |
Config file path (default: config.yml) |
--transport <steam|web-api> |
Master-server transport (overrides config key steam.transport); see Master server transports |
--steam-username <U> |
Steam username (overrides config/env); required for steam transport |
--steam-password <P> |
Steam password (overrides config/env); required for steam transport |
--steam-webapi-key <K> |
Steam Web API key (overrides config/env); required for web-api transport |
--log-level <LEVEL> |
Log level: trace, debug, info, warn, error, critical (default: info); trace surfaces detailed per-app fan-out query statistics |
--include-fakeip |
Opt-in: also query SDR/fake-IP (169.254.*) servers via QueryByFakeIP instead of dropping them (config: fakeip.enabled, env: BLASTER_FAKEIP); see Fake-IP / SDR servers |
--help |
Show help |
Create a YAML file like:
database:
host: localhost
username: root
password: YourPassword
dbname: blaster_stats
steam:
username: myuser
password: mypass
# Optional — choose master-server transport. Values: steam (default), web-api
# transport: web-api
# webapi_key: YOURKEYHERE
# Optional — drop IPs advertising more than this many servers as redirect/spam farms
# (default: 100; 0 disables). See "Spam-farm filtering" below.
# max_servers_per_host: 100
fakeip:
# Optional — set to true to query SDR/fake-IP servers via QueryByFakeIP
# enabled: false--game maps to:
hl1-> game id1hl2-> game id2
Steam credentials, transport settings, and fake-IP behaviour for runtime can come from:
- CLI args (
--steam-username,--steam-password,--transport,--steam-webapi-key,--include-fakeip) - Config file (
steam.username,steam.password,steam.transport,steam.webapi_key,fakeip.enabled) for AmStats - Environment variables (
BLASTER_STEAM_USERNAME,BLASTER_STEAM_PASSWORD,BLASTER_STEAM_TRANSPORT,BLASTER_STEAM_WEBAPI_KEY,BLASTER_FAKEIP)
Some Valve game servers are hosted behind Steam Datagram Relay (SDR) and advertise a link-local "fake IP" address in the 169.254.0.0/16 range. These addresses are unreachable over ordinary UDP, so Blaster drops them by default. Passing --include-fakeip opts in to querying them via GameServers.QueryByFakeIP, using whichever transport is selected (Steam CM connection or Web API). This fake-IP pass runs concurrently with the normal UDP A2S pass, so overall collection time is not significantly increased. For some games — TF2 is a prominent example — fake-IP servers are a large fraction of the total server population, and omitting them will materially under-count results.
Valve master lists are heavily polluted with redirect "farms": a single IP answering on thousands of sequential ports, each impersonating a server, to flood the list and funnel players. Blaster flags these during the master fan-out, using the (harder-to-spoof) counts the GMS reports — no extra A2S query — with two signals:
- Port farm: an IP advertising more than
max-servers-per-hostservers (default100) is treated as one operator stuffing the list, and the whole host is dropped. - Impossible player count: a server reporting more than 130 players is dropped on its own (no real Valve game is realistically this full — most cap at 64, TF2 ~100, GMod ~128), but its host is only condemned if it also trips the port-farm threshold, so a lone faker on a shared hosting IP doesn't take its legitimate neighbours down with it.
Flagged hosts are dropped before they reach the output, and fed back into the fan-out as a master-server NOR exclusion so deeper queries stop returning them.
SDR / fake-IP (169.254.0.0/16) servers are exempt from this filter: they legitimately share addresses across the SDR network, so they are never counted toward the per-host threshold or culled. (Whether they are queried at all is controlled separately by --include-fakeip; see Fake-IP / SDR servers.)
Configuration:
- CLI:
--max-servers-per-host <N>orBLASTER_MAX_SERVERS_PER_HOST. - AmStats:
steam.max_servers_per_hostinconfig.ymlorBLASTER_MAX_SERVERS_PER_HOST(no CLI flag). - Default is
100; set to0to disable the filter entirely.
Blaster.NET supports two transports for querying the Steam master server:
steam(default): Opens a live SteamKit2 connection to the Steam Game Management Server (GMS) using a Steam account. Requires--steam-username/--steam-password(or their config/env equivalents).web-api: Queries the Steam Web API (IGameServersService/GetServerList) over HTTPS instead of a direct Steam connection. Requires a Steam Web API key via--steam-webapi-key(orBLASTER_STEAM_WEBAPI_KEY). No Steam account is needed. The Web API is subject to a documented rate limit of approximately 200 requests per 5 minutes; the client enforces a minimum 1.5-second interval between requests and handles HTTP 429 responses gracefully by honoring theRetry-Afterheader before retrying.