Skip to content

Architecture

Sascha Greuel edited this page Oct 2, 2026 · 3 revisions

Architecture

Home · Adding a protocol · Tests and fixtures

GameQ separates orchestration, server configuration, protocol parsing, network transport, and result filtering. Understanding those boundaries makes protocol changes safer.

flowchart LR
    A[Application] --> B[GameQ orchestrator]
    B --> C[Server definitions]
    C --> D[Protocol instances]
    D --> E[Query packets]
    E --> F[Native socket or configured HTTP client]
    F --> G[Raw responses]
    G --> D
    D --> H[Protocol-native Result]
    H --> I[Filters and gq metadata]
    I --> A
Loading

Core classes

GameQ\GameQ

The public facade:

  • stores servers and global options;
  • owns the configured HTTP client and propagates it to existing and future protocol instances;
  • chunks servers into bounded batches;
  • coordinates challenge, initial, and follow-up query rounds;
  • delegates native sockets to a query implementation;
  • parses each response and applies filters;
  • returns results keyed by server ID.

Use a normal new GameQ() instance in new code. GameQ::factory() remains for compatibility but global singleton-style access is unnecessary for most applications.

GameQ\Server

Server validates type, address, client port, query port, ID, and per-server options. It dynamically resolves an identifier such as css to GameQ\Protocols\Css and owns that protocol instance.

The selected class controls the default query-port calculation. Server::protocolInstance() is the non-null accessor; Server::protocol() is deprecated but retained for 4.x compatibility.

GameQ\Protocol

The protocol base class defines:

  • packet names and transport constants;
  • query-port offsets and join-link templates;
  • response storage;
  • challenge replacement;
  • response-driven follow-up packets;
  • common normalization helpers;
  • injectable HTTP transport access through setHttpClient() and getHttpClient();
  • the processResponse() contract.

Named game classes often extend a shared family such as Source, Gamespy3, Quake3, or Unreal2 and override only metadata, port offsets, or normalization.

HTTP-backed protocols can extend GameQ\Protocols\JsonHttp. This base queries a JSON status endpoint at the configured server and query port through GameQ's configured HTTP client. Protocols backed by an official public server list can instead extend GameQ\Protocols\OfficialDirectory, which downloads and caches a validated HTTPS directory before selecting the configured server. Purpose-built HTTP protocols use the same transport for authentication and multi-request workflows. See HTTP and directory protocols.

GameQ\Http

The HTTP layer added in GameQ 5.2 separates protocol parsing from the concrete HTTP implementation:

  • ClientInterface is the transport contract;
  • Request carries the method, validated URL and headers, body, timeout, and decoded response-size limit;
  • Response carries the status, body, and headers;
  • CurlClient is the standalone default;
  • Psr18Client adapts PSR-18/PSR-17 implementations;
  • GuzzleClient adds Guzzle-specific per-request limits and transport policy.

Applications can inject a transport with GameQ::setHttpClient(). Protocols should call sendHttpRequest() instead of using cURL, Guzzle, or another HTTP library directly.

GameQ\Query\Native

The native query implementation opens UDP/TCP/TLS/SSL streams, writes non-HTTP protocol packets, waits for responses, and returns them grouped by socket. It enforces response-size and packet-count limits and keeps TCP records until a complete response is available.

Some protocols perform HTTPS, REST, or master-list work in beforeSend() instead of sending a native packet. They still return data through the same protocol/result pipeline and use the configured HTTP client. OfficialDirectory, JsonHttp, authenticated APIs, and legacy HTTP packet protocols all share this transport boundary.

GameQ\Buffer

Buffer provides bounds-checked binary reads for strings, integers, and floats. Parser code should use it instead of manual unchecked offsets whenever the format is binary.

An out-of-bounds read is a protocol error, not a condition to suppress. It normally indicates a truncated response, the wrong type/port, or a changed wire format.

GameQ\Result

Result stores general values and assembles player/team rows. Protocol parsers return Result::fetch() or another array<string, mixed>.

Filters

Filters run after parsing. The default normalize filter adds cross-game fields; optional filters remove color codes or format duration values. See filters.

Query lifecycle

  1. addServer() validates the definition and constructs the protocol.
  2. process() divides servers according to max_servers_per_batch.
  3. Challenge-capable protocols send and apply their initial challenge.
  4. Each protocol runs beforeSend() and supplies its initial packets.
  5. HTTP-backed protocols use the configured client; native packet responses are collected through the socket query implementation.
  6. getFollowUpPackets() can request more data, bounded by max_follow_up_rounds.
  7. processResponse() validates and parses all accumulated responses.
  8. GameQ adds online/address/port/type metadata.
  9. Filters run and the result keys are sorted.

Compatibility boundary

Public and protected APIs are compatibility-sensitive because third-party protocols extend GameQ classes. Prefer:

  • adding a new method over changing an existing signature;
  • a documented deprecation and replacement over removal;
  • private helpers for new implementation details;
  • tests for child protocols when changing a shared family;
  • composer bc-check before finalizing public/protected changes.

Shared protocol changes must be validated against every child fixture, not only the game that motivated the change.

Clone this wiki locally