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:
@@ -3,6 +3,7 @@ import {
|
||||
parseInfoLine,
|
||||
parsePlayerCount,
|
||||
parsePlayerRoster,
|
||||
parseStatusHeader,
|
||||
formatRawReply
|
||||
} from '../../src/lib/server/integrations/rcon.js';
|
||||
|
||||
@@ -119,6 +120,46 @@ describe('parsePlayerRoster', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseStatusHeader', () => {
|
||||
it('extracts key:value header lines from a Plutonium status body', () => {
|
||||
const body = [
|
||||
'print',
|
||||
'map: mp_kino',
|
||||
'num score ping guid name',
|
||||
'--- ----- ---- -------- ----',
|
||||
' 0 12 45 abc123def456 joe'
|
||||
].join('\n');
|
||||
const h = parseStatusHeader(body);
|
||||
expect(h.map).toBe('mp_kino');
|
||||
// Header keys stop at the scoreboard — guids/names must not leak in.
|
||||
expect(h.abc123def456).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles builds that include gametype or other dvars in the header', () => {
|
||||
const body = [
|
||||
'print',
|
||||
'map: mp_ascension',
|
||||
'gametype: zclassic',
|
||||
'sv_maxclients: 4',
|
||||
'--- --- ---',
|
||||
''
|
||||
].join('\n');
|
||||
const h = parseStatusHeader(body);
|
||||
expect(h.map).toBe('mp_ascension');
|
||||
expect(h.gametype).toBe('zclassic');
|
||||
expect(h.sv_maxclients).toBe('4');
|
||||
});
|
||||
|
||||
it('returns empty object for a statusResponse (no colon header)', () => {
|
||||
const body = [
|
||||
'statusResponse',
|
||||
'\\mapname\\mp_kino\\g_gametype\\zclassic',
|
||||
'10 45 "joe"'
|
||||
].join('\n');
|
||||
expect(parseStatusHeader(body)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatRawReply', () => {
|
||||
it('renders a small buffer as hex + printable ASCII with an offset', () => {
|
||||
const buf = Buffer.from([0xff, 0xff, 0xff, 0xff, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x0a]);
|
||||
|
||||
Reference in New Issue
Block a user