-
-
Notifications
You must be signed in to change notification settings - Fork 1
Adding a Protocol
Home · Architecture · Tests and fixtures
Start by identifying whether the game uses an existing family. A small named subclass is safer than copying a mature parser.
For a Source-compatible game, the class may need only metadata and a port offset:
<?php
namespace GameQ\Protocols;
/**
* Example Game protocol.
*
* @author Your Name <you@example.com>
*/
class Examplegame extends Source
{
protected string $name = 'examplegame';
protected string $name_long = 'Example Game';
protected int $port_diff = 1;
}The filename and class are Examplegame.php / Examplegame: only the first character is capitalized because Server resolves ucfirst(strtolower($type)) on case-sensitive systems.
Confirm protocol compatibility from actual wire data and official documentation where available. Marketing claims such as “uses Steam” do not prove that a server implements A2S or uses a particular port.
A full protocol class normally defines:
-
$name,$name_long,$protocol, and$transport; -
$state, initiallySTATE_BETAuntil representative fixtures exist; -
$port_diffor an overriddenfindQueryPort(); -
$packetsfor initial requests; - a join-link template where a stable URI scheme exists;
- a normalization map;
-
processResponse()with explicit validation; -
beforeSend(), challenge handling, orgetFollowUpPackets()only when required.
Return an empty array when a valid query establishes that the server is unavailable. Throw ProtocolException for malformed, contradictory, truncated, oversized, or unsupported responses. Do not turn malformed data into plausible partial fields silently.
- Check headers, declared lengths, counts, offsets, and terminators before consuming data.
- Use
Bufferfor binary parsing. - Bound response sizes, packet counts, decompression output, pagination, and loops.
- Use
JSON_THROW_ON_ERRORfor JSON. - Use
LIBXML_NONETand internal libxml error handling for untrusted XML. - Restrict cURL protocols, status codes, response sizes, redirects, and timeouts.
- Do not use repeated
array_merge()in parser loops; append values or assemble once. - Preserve ordering where a protocol uses sequence numbers or paginated offsets.
- Avoid suppressing warnings unless the external API cannot be called safely another way and every return value is checked.
A change to Source, GameSpy, Quake, Unreal, Doom 3, Frostbite, RakNet, or another parent affects many games. Before changing shared behavior:
- identify every subclass;
- preserve initial packets and legacy branches that already work;
- add a fixture for the new case;
- run all child protocol tests;
- run
composer bc-checkfor public/protected changes; - document the behavior and compatibility impact.
Do not make a child protocol “correct” by breaking the parent's established children.
Retain useful native fields and map common values:
protected array $normalize = [
'general' => [
'hostname' => 'server_name',
'mapname' => 'map',
'numplayers' => 'player_count',
'maxplayers' => 'max_players',
],
'player' => [
'name' => 'name',
'score' => 'score',
],
];Do not invent a universal meaning for a field that the protocol does not provide. Use null/absence rather than misleading zeroes or empty strings.
Every new identifier needs a test class, representative fixtures, malformed-input coverage for a new parser, and an entry in the changelog. Update:
- supported identifiers;
- protocol options for non-obvious ports, tokens, REST/master requirements, or switches;
- the README only when the feature belongs in the project overview.
Run the complete workflow in tests and fixtures.
Getting started
Configuration
Guides
Reference
Development
Migration