Use status for the RCON probe (Plutonium doesn't wrap-recognize getstatus)
The hex dump surfaced Plutonium's actual reply: ff ff ff ff 70 72 69 6e 74 0a ....print. 55 6e 6b 6e 6f 77 6e 20 63 6f Unknown co 6d 6d 61 6e 64 20 22 67 65 74 mmand "get 73 74 61 74 75 73 22 0a status". Auth was fine — Plutonium just doesn't recognize `getstatus` as an rcon subcommand. In Q3-derived engines, `getstatus` is an anonymous query (no rcon wrapper); we were sending `rcon <password> getstatus`, which made Plutonium treat it as an unknown subcommand. Switch rconStatusResult to use `status`, same command rconGetPlayerCount has been using successfully all along. The response is a `print` body with a `key: value` header (`map: mp_kino\n...`) followed by the scoreboard, not the `\info\` line the statusResponse dialect uses. New parseStatusHeader extracts key:value dvars from the header, stopping at the `--- ---` separator (or the first player row) so guids and names can't leak into the metadata. rconStatusResult now merges header values with the statusResponse info line so both dialects produce map/gametype in the RconStatus shape. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,25 @@ export function parseInfoLine(text: string): Record<string, string> {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse `key: value` header lines from a `status` (print) response, stopping
|
||||
* at the scoreboard `--- ---` separator so player rows (which can contain
|
||||
* colons in guids or names) don't leak into the metadata. Plutonium T5's
|
||||
* `status` puts things like `map: mp_kino` and (on some builds) gametype
|
||||
* in the header.
|
||||
*/
|
||||
export function parseStatusHeader(text: string): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
for (const rawLine of text.split(/\r?\n/)) {
|
||||
const line = rawLine.trim();
|
||||
if (/^-+\s+-+/.test(line)) break; // scoreboard row — stop
|
||||
if (/^\d+\s+/.test(line)) break; // player row that snuck in
|
||||
const m = /^([a-z][a-z0-9_]*)\s*:\s*(.+)$/i.exec(line);
|
||||
if (m) out[m[1].toLowerCase()] = m[2].trim();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export interface RconPlayer {
|
||||
num: number | null; // client slot — print dialect only
|
||||
score: number;
|
||||
@@ -292,9 +311,13 @@ export async function rconStatusResult(server: string): Promise<RconStatusResult
|
||||
}
|
||||
|
||||
const ep = res.endpoint;
|
||||
// `getstatus` is anonymous on most CoD builds but Plutonium requires the
|
||||
// authenticated rcon route for reliable results, so we ask via rcon.
|
||||
const send = await sendRcon(ep.host, ep.port, 'getstatus');
|
||||
// Plutonium T5 doesn't recognize `getstatus` as an rcon subcommand
|
||||
// ("Unknown command"), so we use `status` — a print-body response with
|
||||
// a header table plus one row per player. Q3-derived engines that
|
||||
// *do* answer statusResponse (via anonymous `getstatus` query) would
|
||||
// give richer info-line dvars, but every engine we care about supports
|
||||
// `status` under the rcon wrapper.
|
||||
const send = await sendRcon(ep.host, ep.port, 'status');
|
||||
if (!send.ok) {
|
||||
return { ok: false, endpoint: ep, reason: send.reason, detail: send.detail };
|
||||
}
|
||||
@@ -332,13 +355,16 @@ export async function rconStatusResult(server: string): Promise<RconStatusResult
|
||||
}
|
||||
return { ok: false, endpoint: ep, reason: 'bad-reply', detail, rawReply: formatRawReply(send.buf) };
|
||||
}
|
||||
const header = parseStatusHeader(text);
|
||||
return {
|
||||
ok: true,
|
||||
endpoint: ep,
|
||||
status: {
|
||||
// Prefer statusResponse `\info\` line values when present, fall
|
||||
// back to print-body `key: value` header lines otherwise.
|
||||
players,
|
||||
map: info.mapname ?? info.sv_mapname ?? null,
|
||||
gametype: info.g_gametype ?? null,
|
||||
map: info.mapname ?? info.sv_mapname ?? header.map ?? header.mapname ?? header.sv_mapname ?? null,
|
||||
gametype: info.g_gametype ?? header.g_gametype ?? header.gametype ?? null,
|
||||
roster: parsePlayerRoster(text)
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user