Skip to content

Instantly share code, notes, and snippets.

@Cynosphere
Last active October 29, 2024 21:00
Show Gist options
  • Save Cynosphere/7ac45bd91175071f04989479d974aa26 to your computer and use it in GitHub Desktop.
Save Cynosphere/7ac45bd91175071f04989479d974aa26 to your computer and use it in GitHub Desktop.
sourcequery nodejs
const dgram = require("dgram");
const ip = process.argv[2];
const port = process.argv[3] || 27015;
if (!ip) {
console.log("giv ip");
process.exit(1);
}
function dumpPacket(view) {
let bytes = "";
let str = "";
let allBytes = "";
for (let i = 0; i < view.byteLength; i++) {
if ((i > 0 && i % 16 == 0) || i == view.byteLength - 1) {
console.log(bytes.padEnd(48, " ") + " " + str);
allBytes += bytes;
bytes = "";
str = "";
}
const byte = view.getUint8(i);
bytes += byte.toString(16).padStart(2, "0").toUpperCase() + " ";
str += byte < 0x10 || byte == 0x7f ? "." : String.fromCharCode(byte);
}
console.log(allBytes);
}
const request_info = [
0xff, 0xff, 0xff, 0xff, 0x54, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x20,
0x51, 0x75, 0x65, 0x72, 0x79, 0x00,
];
const request_rules = [0xff, 0xff, 0xff, 0xff, 0x56];
const rulePackets = [];
let chunks = -1;
function processRules() {
const data = rulePackets.flat();
const dataArray = new Uint8Array(data);
const rulesMsg = new DataView(dataArray.buffer);
//dumpPacket(rulesMsg);
if (rulesMsg.getUint8(4) == 0x45) {
let idx = 5;
const rule_count = rulesMsg.getUint16(idx, true);
idx += 2;
console.log("cvar count:", rule_count);
const rules = [];
for (i = 0; i < rule_count; i++) {
if (idx >= rulesMsg.byteLength) break;
const _name = [];
while (rulesMsg.getUint8(idx) != 0) {
_name.push(rulesMsg.getUint8(idx));
idx++;
}
idx++;
const name = String.fromCharCode(..._name);
if (idx >= rulesMsg.byteLength) break;
const _value = [];
while (rulesMsg.getUint8(idx) != 0) {
_value.push(rulesMsg.getUint8(idx));
idx++;
}
idx++;
const value = String.fromCharCode(..._value);
rules.push({name, value});
}
console.log("====== CVars ======");
for (const rule of rules) {
console.log(`${rule.name} = ${rule.value}`);
}
} else {
console.log("malformed rules packet o7");
}
}
function handlerRules(message, resolve, reject, wrapper, client) {
const msg = new DataView(message.buffer);
//console.log("rules", message);
if (msg.getUint8(4) == 0x41) {
const challenge = [msg.getUint8(5), msg.getUint8(6), msg.getUint8(7), msg.getUint8(8)];
//console.log("got challenge", challenge);
client.send(Buffer.from([...request_rules, ...challenge]), port, ip, function (err) {
if (err) {
client.close();
reject(err);
}
});
} else if (msg.getUint8(0) == 0xfe && msg.getUint8(1) == 0xff && msg.getUint8(2) == 0xff && msg.getUint8(3) == 0xff) {
chunks = msg.getUint8(8);
const chunkIdx = msg.getUint8(9);
const chunk = [];
for (let i = 12; i < msg.byteLength; i++) {
chunk.push(msg.getUint8(i));
}
rulePackets[chunkIdx] = chunk;
}
if (chunks != -1 && rulePackets.length == chunks) {
client.close();
processRules();
client.off("message", wrapper);
resolve(true);
}
}
function a2sRules() {
//console.log("requesting rules");
return new Promise((resolve, reject) => {
const client = dgram.createSocket("udp4");
client.send(Buffer.from([...request_rules, 0xff, 0xff, 0xff, 0xff]), port, ip, function (err) {
if (err) {
client.close();
reject(err);
}
});
function wrapper(message) {
handlerRules(message, resolve, reject, wrapper, client);
}
client.on("message", wrapper);
});
}
function handlerInfo(message, resolve, reject, wrapper, client) {
const msg = new DataView(message.buffer);
//console.log("info", message);
if (msg.getUint8(4) == 0x41) {
const challenge = [msg.getUint8(5), msg.getUint8(6), msg.getUint8(7), msg.getUint8(8)];
//console.log("got challenge", challenge);
client.send(Buffer.from([...request_info, ...challenge]), port, ip, function (err) {
if (err) {
client.close();
reject(err);
}
});
} else if (msg.getUint8(4) == 0x49) {
//console.log("got query response");
//dumpPacket(msg);
const protocolVer = msg.getUint8(5);
console.log("protocol version:", protocolVer);
let idx = 6;
const name = [];
while (msg.getUint8(idx) != 0) {
name.push(msg.getUint8(idx));
idx++;
}
console.log("name:", String.fromCharCode(...name));
idx++;
const map = [];
while (msg.getUint8(idx) != 0) {
map.push(msg.getUint8(idx));
idx++;
}
console.log("map:", String.fromCharCode(...map));
idx++;
const folder = [];
while (msg.getUint8(idx) != 0) {
folder.push(msg.getUint8(idx));
idx++;
}
console.log("folder:", String.fromCharCode(...folder));
idx++;
const game = [];
while (msg.getUint8(idx) != 0) {
game.push(msg.getUint8(idx));
idx++;
}
console.log("game:", String.fromCharCode(...game));
idx++;
console.log("steam id:", msg.getUint16(idx, true));
idx += 2;
console.log("players:", msg.getUint8(idx));
idx++;
console.log("max players:", msg.getUint8(idx));
idx++;
console.log("bots:", msg.getUint8(idx));
idx++;
console.log("type:", String.fromCharCode(msg.getUint8(idx)));
idx++;
console.log("os:", String.fromCharCode(msg.getUint8(idx)));
idx++;
console.log("visibility:", msg.getUint8(idx));
idx++;
console.log("vac:", msg.getUint8(idx));
idx++;
const version = [];
while (msg.getUint8(idx) != 0) {
version.push(msg.getUint8(idx));
idx++;
}
console.log("version:", String.fromCharCode(...version));
idx++;
const edf = msg.getUint8(idx);
idx++;
console.log("edf is:", edf);
console.log("\n====== EDF Values ======");
if (edf & 0x80) {
console.log("port:", msg.getUint16(idx, true));
idx += 2;
}
if (edf & 0x10) {
console.log("steamid:", msg.getUint32(idx, true));
idx += 4;
}
if (edf & 0x40) {
console.log("sourcetv port:", msg.getUint16(idx, true));
idx += 2;
idx += 4; // ???
const stvname = [];
while (msg.getUint8(idx) != 0) {
stvname.push(msg.getUint8(idx));
idx++;
}
console.log("sourcetv name:", String.fromCharCode(...stvname));
idx++;
}
if (edf & 0x20) {
const tags = [];
while (msg.getUint8(idx) != 0) {
tags.push(msg.getUint8(idx));
idx++;
}
console.log("tags:", String.fromCharCode(...tags));
idx++;
}
if (edf & 0x01) {
console.log("extended app id:", msg.getBigUint64(idx, true));
idx += 8;
}
client.close();
client.off("message", wrapper);
resolve(true);
}
}
function a2sInfo() {
//console.log("requesting info");
return new Promise((resolve, reject) => {
const client = dgram.createSocket("udp4");
client.send(Buffer.from(request_info), port, ip, function (err) {
if (err) {
client.close();
reject(err);
}
});
function wrapper(message) {
handlerInfo(message, resolve, reject, wrapper, client);
}
client.on("message", wrapper);
});
}
(async () => {
await a2sInfo();
console.log("");
await a2sRules();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment