Super mega-commit
Organize files Rewrite readme for new game IDs and command line Add command line access Replace some dependencies that required binaries with simpler alternatives Switch gbxremote back to upstream, Closes #2 Moved simple aliases into an alias file, rather than seperate files for each Patched nearly every protocol variant with tons of bug fixes Re-tested every combination of server and protocol types except nadeo Added alternative minecraft query check (minecraftping) Fixed mutant factions query Fixed valve gold not working at all Stripped colors more reliably from protocols that support colors Added a couple more fields to ut2004 and killing floor and more that I probably forgot. This shouldn't break compatibility too bad -- at the most, some game IDs may have changed.
This commit is contained in:
parent
a89fb7bbdf
commit
c82554ad1a
|
@ -0,0 +1 @@
|
|||
/node_modules
|
115
README.md
115
README.md
|
@ -1,7 +1,7 @@
|
|||
node-GameDig - Game Server Query Library
|
||||
---
|
||||
|
||||
Usage
|
||||
Usage from Node.js
|
||||
---
|
||||
|
||||
```shell
|
||||
|
@ -24,7 +24,7 @@ Gamedig.query(
|
|||
|
||||
### Input Parameters
|
||||
|
||||
* **type**: One of the types from the protocols folder
|
||||
* **type**: One of the game IDs listed in the game list below
|
||||
* **host**
|
||||
* **port**: (optional) Uses the protocol default if not set
|
||||
* **notes**: (optional) Passed through to output
|
||||
|
@ -59,45 +59,84 @@ Some servers may return an additional player count number, which may be present
|
|||
Supported Games
|
||||
---
|
||||
|
||||
###Armagetron
|
||||
|
||||
###Gamespy 3 Protocol
|
||||
* Minecraft
|
||||
* Unreal Tournament 3
|
||||
|
||||
###GoldSrc Engine
|
||||
* Half Life: Death Match
|
||||
* Ricochet
|
||||
* Counter-Strike: 1.6
|
||||
* \+ others
|
||||
|
||||
###Nadeo Protocol
|
||||
* Alien Swarm (alienswarm)
|
||||
* Armagetron (armagetron)
|
||||
* Build and Shoot (buildandshoot)
|
||||
* Counter-Strike 1.6 (cs16)
|
||||
* Counter-Strike: Source (css)
|
||||
* Counter-Strike: Global Offensive (csgo)
|
||||
* Dino D-Day (dinodday)
|
||||
* Garry's Mod (garrysmod)
|
||||
* The Hidden: Source (hidden)
|
||||
* Just Cause Multiplayer (jcmp)
|
||||
* Killing Floor (killingfloor)
|
||||
* KzMod (kzmod)
|
||||
* Left 4 Dead (left4dead)
|
||||
* Left 4 Dead 2 (left4dead2)
|
||||
* Minecraft (minecraft)
|
||||
```
|
||||
Some minecraft servers may not respond to a typical status query. If this is the case, try using the
|
||||
'minecraftping' server type instead, which uses a less accurate but more reliable solution.
|
||||
```
|
||||
* Mutant Factions (mutantfactions)
|
||||
* Natural Selection (ns)
|
||||
* Natural Selection 2 (ns2)
|
||||
* No More Room in Hell (nmrih)
|
||||
* Nuclear Dawn (nucleardawn)
|
||||
* Quake 2 (quake2)
|
||||
* Quake 3 (quake3)
|
||||
* Ricochet (ricochet)
|
||||
* Rust (rust)
|
||||
* The Ship (ship)
|
||||
* ShootMania (shootmania)
|
||||
```
|
||||
Requires additional parameters: login, password
|
||||
```
|
||||
* Trackmania Forever
|
||||
* Trackmania 2
|
||||
* Shootmania
|
||||
|
||||
###Quake 2 Protocol
|
||||
* Quake 2
|
||||
|
||||
###Quake 3 Protocol
|
||||
* Quake 3 Arena
|
||||
* Quake 3 Team Arena
|
||||
* Warsow
|
||||
|
||||
###Source Engine
|
||||
* Counter-Strike: Source
|
||||
* Counter-Strike: Global Offensive
|
||||
* Team Fortress 2
|
||||
* \+ others
|
||||
|
||||
###Terraria (tshock)
|
||||
* Starbound (starbound)
|
||||
* Suicide Survival (suicidesurvival)
|
||||
* Sven Coop (svencoop)
|
||||
* Synergy (synergy)
|
||||
* Team Fortress 2 (tf2)
|
||||
* Terraria (terraria)
|
||||
```
|
||||
Requires additional parameter: token
|
||||
Requires tshock server mod, and an additional parameter: token
|
||||
```
|
||||
* TrackMania 2 (trackmania2)
|
||||
```
|
||||
Requires additional parameters: login, password
|
||||
```
|
||||
* TrackMania Forever (trackmaniaforever)
|
||||
```
|
||||
Requires additional parameters: login, password
|
||||
```
|
||||
* Unreal Tournament 2004 (ut2004)
|
||||
* Unreal Tournament 3 (ut3)
|
||||
* Warsow (warsow)
|
||||
|
||||
Don't see your game listed here?
|
||||
1. Let us know so we can fix it
|
||||
2. You can try using some common query protocols directly by using one of these server types:
|
||||
* protocol-gamespy3
|
||||
* protocol-nadeo
|
||||
* protocol-quake2
|
||||
* protocol-quake3
|
||||
* protocol-unreal2
|
||||
* protocol-valve
|
||||
* protocol-valvegold
|
||||
|
||||
Usage from Command Line
|
||||
---
|
||||
|
||||
Want to integrate server queries from a batch script or other programming language?
|
||||
You'll still need npm to install gamedig:
|
||||
```shell
|
||||
npm install gamedig -g
|
||||
```
|
||||
|
||||
###Unreal 2 Protocol
|
||||
* Killing Floor
|
||||
* Unreal Tournament 2004
|
||||
After installing gamedig globally, you can call gamedig via the command line
|
||||
using the same parameters mentioned in the API above:
|
||||
```shell
|
||||
gamedig --type minecraft --host mc.example.com --port 11234
|
||||
```
|
||||
|
||||
The output of the command will be in JSON format.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var argv = require('optimist').argv;
|
||||
|
||||
var debug = argv.debug;
|
||||
delete argv.debug;
|
||||
|
||||
var options = {};
|
||||
for(var key in argv) {
|
||||
var value = argv[key];
|
||||
if(
|
||||
key == '_'
|
||||
|| key.charAt(0) == '$'
|
||||
|| (typeof value != 'string' && typeof value != 'number')
|
||||
)
|
||||
continue;
|
||||
options[key] = value;
|
||||
}
|
||||
|
||||
var Gamedig = require('../lib/index');
|
||||
if(debug) Gamedig.debug = true;
|
||||
Gamedig.query(
|
||||
options,
|
||||
function(state) {
|
||||
console.log(state);
|
||||
}
|
||||
);
|
|
@ -0,0 +1,29 @@
|
|||
# id | pretty | protocol | port?
|
||||
|
||||
alienswarm|Alien Swarm|valve
|
||||
csgo|Counter-Strike: Global Offensive|valve
|
||||
css|Counter-Strike: Source|valve
|
||||
cs16|Counter-Strike 1.6|valvegold
|
||||
dinodday|Dino D-Day|valve
|
||||
garrysmod|Garry's Mod|valve
|
||||
hidden|The Hidden: Source|valve
|
||||
kzmod|KzMod|valve
|
||||
left4dead|Left 4 Dead|valve
|
||||
left4dead2|Left 4 Dead 2|valve
|
||||
nmrih|No More Room in Hell|valve
|
||||
ns|Natural Selection|valvegold
|
||||
ns2|Natural Selection 2|valve|27016
|
||||
nucleardawn|Nuclear Dawn|valve
|
||||
quake2|Quake 2|quake2
|
||||
quake3|Quake 3|quake3
|
||||
ricochet|Ricochet|valvegold
|
||||
rust|Rust|valve|28016
|
||||
ship|The Ship|valve
|
||||
shootmania|Shootmania|nadeo
|
||||
starbound|Starbound|valve
|
||||
suicidesurvival|Suicide Survival|valve
|
||||
svencoop|Sven Coop|valvegold
|
||||
synergy|Synergy|valve
|
||||
tf2|Team Fortress 2|valve
|
||||
trackmania2|Trackmania 2|nadeo
|
||||
trackmaniaforever|Trackmania Forever|nadeo
|
|
@ -1,4 +1,4 @@
|
|||
module.exports = require('./core').extend({
|
||||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Armagetron';
|
||||
|
@ -18,7 +18,7 @@ module.exports = require('./core').extend({
|
|||
|
||||
state.raw.port = self.readUInt(reader);
|
||||
state.raw.hostname = self.readString(reader,buffer);
|
||||
state.name = self.readString(reader,buffer);
|
||||
state.name = self.stripColorCodes(self.readString(reader,buffer));
|
||||
state.raw.numplayers = self.readUInt(reader);
|
||||
state.raw.versionmin = self.readUInt(reader);
|
||||
state.raw.versionmax = self.readUInt(reader);
|
||||
|
@ -29,10 +29,12 @@ module.exports = require('./core').extend({
|
|||
var list = players.split('\n');
|
||||
for(var i = 0; i < list.length; i++) {
|
||||
if(!list[i]) continue;
|
||||
state.players.push({name:list[i]});
|
||||
state.players.push({
|
||||
name:self.stripColorCodes(list[i])
|
||||
});
|
||||
}
|
||||
|
||||
state.raw.options = self.readString(reader,buffer);
|
||||
state.raw.options = self.stripColorCodes(self.readString(reader,buffer));
|
||||
state.raw.uri = self.readString(reader,buffer);
|
||||
state.raw.globalids = self.readString(reader,buffer);
|
||||
self.finish(state);
|
||||
|
@ -56,7 +58,9 @@ module.exports = require('./core').extend({
|
|||
if(i+2<len) out += String.fromCharCode(hi);
|
||||
}
|
||||
|
||||
out = out.replace(/0x[0-9a-f]{6}/g,''); // strip color codes
|
||||
return out;
|
||||
},
|
||||
stripColorCodes: function(str) {
|
||||
return str.replace(/0x[0-9a-f]{6}/g,'');
|
||||
}
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
var request = require('request');
|
||||
|
||||
module.exports = require('./core').extend({
|
||||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Build and Shoot';
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
module.exports = require('./protocols/valve').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7777;
|
||||
this.pretty = 'Just Cause 2 Multiplayer';
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// supposedly, gamespy3 is the "official" query protocol for jcmp,
|
||||
// but it's broken (requires singlePacketSplits), and doesn't include player names
|
||||
module.exports = require('./protocols/gamespy3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7777;
|
||||
this.pretty = 'Just Cause 2 Multiplayer';
|
||||
this._singlePacketSplits = true;
|
||||
},
|
||||
finalizeState: function(state) {
|
||||
this._super(state);
|
||||
console.log(state.players.length);
|
||||
console.log(state.raw.numplayers);
|
||||
if(!state.players.length && parseInt(state.raw.numplayers)) {
|
||||
for(var i = 0; i < parseInt(state.raw.numplayers); i++) {
|
||||
state.players.push({});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = require('./protocols/unreal2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7708;
|
||||
this.pretty = 'Killing Floor';
|
||||
},
|
||||
readExtraInfo: function(reader,state) {
|
||||
state.raw.numplayers = reader.uint(4);
|
||||
state.maxplayers = reader.uint(4);
|
||||
state.raw.wavecurrent = reader.uint(4);
|
||||
state.raw.wavetotal = reader.uint(4);
|
||||
}
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
var dns = require('dns');
|
||||
|
||||
module.exports = require('./gamespy3').extend({
|
||||
module.exports = require('./protocols/gamespy3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Minecraft';
|
|
@ -0,0 +1,127 @@
|
|||
var dns = require('dns'),
|
||||
net = require('net'),
|
||||
varint = require('varint');
|
||||
|
||||
function varIntBuffer(num) {
|
||||
return new Buffer(varint.encode(num));
|
||||
}
|
||||
|
||||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Minecraft';
|
||||
this.options.port = 25565;
|
||||
},
|
||||
parseDns: function(host,c) {
|
||||
var self = this;
|
||||
var _super = this._super;
|
||||
function fallback(h) { _super.call(self,h,c); }
|
||||
|
||||
dns.resolve('_minecraft._tcp.'+host, 'SRV', function(err,addresses) {
|
||||
if(err) return fallback(host);
|
||||
if(addresses.length >= 1) {
|
||||
var line = addresses[0];
|
||||
self.options.port = line.port;
|
||||
var srvhost = line.name;
|
||||
|
||||
if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) {
|
||||
self.options.address = srvhost;
|
||||
c();
|
||||
} else {
|
||||
// resolve yet again
|
||||
fallback(srvhost);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return fallback(host);
|
||||
});
|
||||
},
|
||||
reset: function() {
|
||||
this._super();
|
||||
if(this.socket) {
|
||||
this.socket.destroy();
|
||||
delete this.socket;
|
||||
}
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
|
||||
var socket = this.socket = net.connect(
|
||||
this.options.port,
|
||||
this.options.address,
|
||||
function() {
|
||||
|
||||
var portBuf = new Buffer(2);
|
||||
portBuf.writeUInt16BE(self.options.port,0);
|
||||
|
||||
var addressBuf = new Buffer(self.options.address,'utf8');
|
||||
|
||||
var bufs = [
|
||||
varIntBuffer(4),
|
||||
varIntBuffer(addressBuf.length),
|
||||
addressBuf,
|
||||
portBuf,
|
||||
varIntBuffer(1)
|
||||
];
|
||||
self.sendPacket(0,Buffer.concat(bufs));
|
||||
self.sendPacket(0);
|
||||
});
|
||||
socket.setTimeout(10000);
|
||||
socket.setNoDelay(true);
|
||||
|
||||
var received = new Buffer(0);
|
||||
var expectedBytes = 0;
|
||||
socket.on('data', function(data) {
|
||||
received = Buffer.concat([received,data]);
|
||||
if(expectedBytes) {
|
||||
if(received.length >= expectedBytes) {
|
||||
self.allReceived(received,state);
|
||||
}
|
||||
} else if(received.length > 10) {
|
||||
expectedBytes = varint.decode(received);
|
||||
received = received.slice(varint.decode.bytesRead);
|
||||
}
|
||||
});
|
||||
},
|
||||
sendPacket: function(id,data) {
|
||||
if(!data) data = new Buffer(0);
|
||||
var idBuffer = varIntBuffer(id);
|
||||
var out = Buffer.concat([
|
||||
varIntBuffer(data.length+idBuffer.length),
|
||||
idBuffer,
|
||||
data
|
||||
]);
|
||||
this.socket.write(out);
|
||||
},
|
||||
allReceived: function(received,state) {
|
||||
var packetId = varint.decode(received);
|
||||
received = received.slice(varint.decode.bytesRead);
|
||||
|
||||
var strLen = varint.decode(received);
|
||||
received = received.slice(varint.decode.bytesRead);
|
||||
|
||||
var str = received.toString('utf8');
|
||||
var json;
|
||||
try {
|
||||
json = JSON.parse(str);
|
||||
delete json.favicon;
|
||||
} catch(e) {
|
||||
return this.fatal('Invalid JSON');
|
||||
}
|
||||
|
||||
state.raw.version = json.version.name;
|
||||
state.maxplayers = json.players.max;
|
||||
state.raw.description = json.description.text;
|
||||
for(var i = 0; i < json.players.sample.length; i++) {
|
||||
state.players.push({
|
||||
id: json.players.sample[i].id,
|
||||
name: json.players.sample[i].name
|
||||
});
|
||||
}
|
||||
while(state.players.length < json.players.online) {
|
||||
state.players.push({});
|
||||
}
|
||||
|
||||
this.finish(state);
|
||||
}
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
var request = require('request');
|
||||
|
||||
module.exports = require('./core').extend({
|
||||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Mutant Factions';
|
||||
|
@ -22,7 +22,7 @@ module.exports = require('./core').extend({
|
|||
var fields = line.split('::');
|
||||
var ip = fields[2];
|
||||
var port = fields[3];
|
||||
if(ip == this.options.address && port == this.options.port) {
|
||||
if(ip == self.options.address && port == self.options.port) {
|
||||
found = fields;
|
||||
break;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
var EventEmitter = require('events').EventEmitter,
|
||||
dns = require('dns'),
|
||||
async = require('async'),
|
||||
Class = require('../Class'),
|
||||
Reader = require('../reader');
|
||||
Class = require('../../lib/Class'),
|
||||
Reader = require('../../lib/reader');
|
||||
|
||||
module.exports = Class.extend(EventEmitter,{
|
||||
init: function() {
|
||||
|
@ -154,6 +154,8 @@ module.exports = Class.extend(EventEmitter,{
|
|||
if(!('address' in this.options)) return this.fatal('Attempted to send without setting an address');
|
||||
|
||||
if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary');
|
||||
|
||||
if(this.debug) console.log("Sent",buffer,this.options.address,this.options.port);
|
||||
this.udpSocket.send(buffer,0,buffer.length,this.options.port,this.options.address);
|
||||
},
|
||||
_udpResponse: function(buffer) {
|
|
@ -21,28 +21,48 @@ module.exports = require('./core').extend({
|
|||
var key = reader.string();
|
||||
if(!key) break;
|
||||
var value = reader.string();
|
||||
|
||||
// reread the next line if we hit the weird ut3 bug
|
||||
if(value == 'p1073741829') value = reader.string();
|
||||
|
||||
state.raw[key] = value;
|
||||
}
|
||||
|
||||
var mode = '';
|
||||
|
||||
while(!reader.done()) {
|
||||
var mode = reader.string();
|
||||
if(mode.charCodeAt(0) <= 2) mode = mode.substring(1);
|
||||
if(!mode) continue;
|
||||
var offset = 0;
|
||||
reader.skip(1);
|
||||
|
||||
|
||||
while(!reader.done()) {
|
||||
var item = reader.string();
|
||||
if(!item) break;
|
||||
|
||||
if(mode.substr(-1) == '_') {
|
||||
// players
|
||||
state.players.push({name:item})
|
||||
|
||||
if(
|
||||
mode == 'player_'
|
||||
|| mode == 'score_'
|
||||
|| mode == 'ping_'
|
||||
|| mode == 'team_'
|
||||
|| mode == 'deaths_'
|
||||
|| mode == 'pid_'
|
||||
) {
|
||||
if(state.players.length <= offset)
|
||||
state.players.push({});
|
||||
}
|
||||
if(mode == 'player_') state.players[offset].name = item;
|
||||
if(mode == 'score_') state.players[offset].score = item;
|
||||
if(mode == 'ping_') state.players[offset].ping = item;
|
||||
if(mode == 'team_') state.players[offset].team = item;
|
||||
if(mode == 'deaths_') state.players[offset].deaths = item;
|
||||
if(mode == 'pid_') state.players[offset].pid = item;
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
if('hostname' in state.raw) state.name = state.raw.hostname;
|
||||
if('map' in state.raw) state.map = state.raw.map;
|
||||
if('maxplayers' in state.raw) state.maxplayers = state.raw.maxplayers;
|
||||
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
|
||||
|
||||
self.finish(state);
|
||||
|
||||
|
@ -79,7 +99,7 @@ module.exports = require('./core').extend({
|
|||
var id = buffer.readUInt16LE(14);
|
||||
var last = (id & 0x80);
|
||||
id = id & 0x7f;
|
||||
if(last) numPackets = id+1;
|
||||
if(last || self._singlePacketSplits) numPackets = id+1;
|
||||
|
||||
packets[id] = buffer.slice(16);
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
module.exports = require('./quake2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Quake 3';
|
||||
this.options.port = 27960;
|
||||
this.sendHeader = 'getstatus';
|
||||
this.responseHeader = 'statusResponse';
|
||||
},
|
||||
finalizeState: function(state) {
|
||||
state.name = this.stripColors(state.name);
|
||||
for(var i in state.raw) {
|
||||
state.raw[i] = this.stripColors(state.raw[i]);
|
||||
}
|
||||
for(var i = 0; i < state.players.length; i++) {
|
||||
state.players[i].name = this.stripColors(state.players[i].name);
|
||||
}
|
||||
},
|
||||
stripColors: function(str) {
|
||||
return str.replace(/\^(X.{6}|.)/g,'');
|
||||
}
|
||||
});
|
|
@ -14,15 +14,15 @@ module.exports = require('./core').extend({
|
|||
self.sendPacket(0,true,function(b) {
|
||||
var reader = self.reader(b);
|
||||
state.raw.serverid = reader.uint(4);
|
||||
state.raw.ip = reader.pascal();
|
||||
state.raw.ip = self.readUnrealString(reader);
|
||||
state.raw.port = reader.uint(4);
|
||||
state.raw.queryport = reader.uint(4);
|
||||
state.name = reader.pascal();
|
||||
state.map = reader.pascal();
|
||||
state.raw.gametype = reader.pascal();
|
||||
state.raw.numplayers = reader.uint(4);
|
||||
state.maxplayers = reader.uint(4);
|
||||
state.raw.ping = reader.uint(4);
|
||||
state.name = self.readUnrealString(reader,true);
|
||||
self.readUnrealString(reader); // unknown?
|
||||
state.map = self.readUnrealString(reader,true);
|
||||
state.raw.gametype = self.readUnrealString(reader,true);
|
||||
self.readExtraInfo(reader,state);
|
||||
|
||||
c();
|
||||
});
|
||||
},
|
||||
|
@ -32,8 +32,8 @@ module.exports = require('./core').extend({
|
|||
state.raw.mutators = [];
|
||||
state.raw.rules = {};
|
||||
while(!reader.done()) {
|
||||
var key = reader.pascal();
|
||||
var value = reader.pascal();
|
||||
var key = self.readUnrealString(reader,true);
|
||||
var value = self.readUnrealString(reader,true);
|
||||
if(key == 'Mutator') state.raw.mutators.push(value);
|
||||
else state.raw.rules[key] = value;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ module.exports = require('./core').extend({
|
|||
var reader = self.reader(b);
|
||||
while(!reader.done()) {
|
||||
var id = reader.uint(4);
|
||||
var name = reader.pascal();
|
||||
var name = self.readUnrealString(reader,true);
|
||||
var ping = reader.uint(4);
|
||||
var score = reader.uint(4);
|
||||
reader.skip(4);
|
||||
|
@ -65,6 +65,34 @@ module.exports = require('./core').extend({
|
|||
}
|
||||
]);
|
||||
},
|
||||
readExtraInfo: function(reader,state) {
|
||||
if(this.debug) {
|
||||
console.log("UNREAL2 EXTRA INFO:");
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.buffer.slice(reader.i));
|
||||
}
|
||||
},
|
||||
readUnrealString: function(reader, stripColor) {
|
||||
var length = reader.uint(1);
|
||||
var out;
|
||||
if(length < 0x80) {
|
||||
out = reader.string({length:length});
|
||||
} else {
|
||||
length = (length&0x7f)*2;
|
||||
out = length+reader.string({encoding:'ucs2',length:length});
|
||||
}
|
||||
|
||||
if(out.charCodeAt(out.length-1) == 0)
|
||||
out = out.substring(0,out.length-1);
|
||||
|
||||
if(stripColor)
|
||||
out = out.replace(/\x1b...|[\x00-\x1a]/g,'');
|
||||
|
||||
return out;
|
||||
},
|
||||
sendPacket: function(type,required,callback) {
|
||||
var outbuffer = new Buffer([0x79,0,0,0,type]);
|
||||
|
|
@ -6,6 +6,10 @@ module.exports = require('./core').extend({
|
|||
this._super();
|
||||
this.goldsrc = false;
|
||||
this.options.port = 27015;
|
||||
|
||||
// 2006 engines don't pass packet switching size in split packet header
|
||||
// while all others do
|
||||
this._skipSizeInSplitHeader = false;
|
||||
},
|
||||
run: function(state) {
|
||||
|
||||
|
@ -34,9 +38,14 @@ module.exports = require('./core').extend({
|
|||
if(self.goldsrc) state.raw.protocol = reader.uint(1);
|
||||
else state.raw.numbots = reader.uint(1);
|
||||
|
||||
state.raw.listentype = String.fromCharCode(reader.uint(1));
|
||||
state.raw.environment = String.fromCharCode(reader.uint(1));
|
||||
state.password = reader.uint(1);
|
||||
state.raw.listentype = reader.uint(1);
|
||||
state.raw.environment = reader.uint(1);
|
||||
if(!self.goldsrc) {
|
||||
state.raw.listentype = String.fromCharCode(state.raw.listentype);
|
||||
state.raw.environment = String.fromCharCode(state.raw.environment);
|
||||
}
|
||||
|
||||
state.password = !!reader.uint(1);
|
||||
if(self.goldsrc) {
|
||||
state.raw.ismod = reader.uint(1);
|
||||
if(state.raw.ismod) {
|
||||
|
@ -71,12 +80,16 @@ module.exports = require('./core').extend({
|
|||
if(extraFlag & 0x01) state.raw.gameid = reader.uint(8);
|
||||
}
|
||||
|
||||
if(state.raw.protocol == 7 && state.raw.steamappid == 215) {
|
||||
self._skipSizeInSplitHeader = true;
|
||||
}
|
||||
|
||||
c();
|
||||
}
|
||||
);
|
||||
},
|
||||
function(c) {
|
||||
self.sendPacket(0x55,0xffffffff,false,0x41,function(b) {
|
||||
self.sendPacket(self.goldsrc?0x56:0x55,0xffffffff,false,0x41,function(b) {
|
||||
var reader = self.reader(b);
|
||||
challenge = reader.uint(4);
|
||||
c();
|
||||
|
@ -89,7 +102,7 @@ module.exports = require('./core').extend({
|
|||
for(var i = 0; i < num; i++) {
|
||||
reader.skip(1);
|
||||
var name = reader.string();
|
||||
var score = reader.uint(4);
|
||||
var score = reader.int(4);
|
||||
var time = reader.float();
|
||||
|
||||
// connecting players don't could as players.
|
||||
|
@ -131,6 +144,12 @@ module.exports = require('./core').extend({
|
|||
state.raw.rules[key] = value;
|
||||
}
|
||||
c();
|
||||
}, function() {
|
||||
// no rules were returned after timeout --
|
||||
// the server probably has them disabled
|
||||
// ignore the timeout
|
||||
c();
|
||||
return true;
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
|
@ -138,7 +157,7 @@ module.exports = require('./core').extend({
|
|||
}
|
||||
]);
|
||||
},
|
||||
sendPacket: function(type,challenge,payload,expect,callback) {
|
||||
sendPacket: function(type,challenge,payload,expect,callback,ontimeout) {
|
||||
var self = this;
|
||||
|
||||
var challengeLength = challenge === false ? 0 : 4;
|
||||
|
@ -152,6 +171,7 @@ module.exports = require('./core').extend({
|
|||
|
||||
function received(payload) {
|
||||
var type = payload.readUInt8(0);
|
||||
if(self.debug) console.log("Received "+type+" expected "+expect);
|
||||
if(type != expect) return;
|
||||
callback(payload.slice(1));
|
||||
return true;
|
||||
|
@ -175,16 +195,22 @@ module.exports = require('./core').extend({
|
|||
if(self.goldsrc) {
|
||||
id = buffer.readUInt8(8);
|
||||
numPackets = id & 0x0f;
|
||||
id = id & 0xf0 >> 4;
|
||||
id = (id & 0xf0) >> 4;
|
||||
payload = buffer.slice(9);
|
||||
} else {
|
||||
numPackets = buffer.readUInt8(8);
|
||||
id = buffer.readUInt8(9);
|
||||
if(id == 0 && bzip) payload = buffer.slice(20);
|
||||
else payload = buffer.slice(12);
|
||||
var sizeOffset = self._skipSizeInSplitHeader ? 0 : 2;
|
||||
if(id == 0 && bzip) payload = buffer.slice(18+sizeOffset);
|
||||
else payload = buffer.slice(10+sizeOffset);
|
||||
}
|
||||
|
||||
packets[id] = payload;
|
||||
|
||||
if(self.debug) {
|
||||
console.log("Received partial packet id: "+id);
|
||||
console.log("Expecting "+numPackets+" packets, have "+Object.keys(packets).length);
|
||||
}
|
||||
|
||||
if(!numPackets || Object.keys(packets).length != numPackets) return;
|
||||
|
||||
|
@ -198,11 +224,10 @@ module.exports = require('./core').extend({
|
|||
list.push(packets[i]);
|
||||
}
|
||||
var assembled = Buffer.concat(list);
|
||||
var payload = assembled.slice(4);
|
||||
if(bzip) payload = Bzip2.uncompressFile(payload);
|
||||
if(bzip) assembled = new Buffer(Bzip2.decompressFile(assembled));
|
||||
|
||||
return received(payload);
|
||||
return received(assembled.slice(4));
|
||||
}
|
||||
});
|
||||
},ontimeout);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = require('./valve').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.goldsrc = true;
|
||||
}
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
var request = require('request');
|
||||
|
||||
module.exports = require('./core').extend({
|
||||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Terraria';
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = require('./protocols/unreal2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7778;
|
||||
this.pretty = 'Unreal Tournament 2004';
|
||||
},
|
||||
readExtraInfo: function(reader,state) {
|
||||
reader.skip(18);
|
||||
state.raw.numplayers = reader.uint(4);
|
||||
state.maxplayers = reader.uint(4);
|
||||
}
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
module.exports = require('./gamespy3').extend({
|
||||
module.exports = require('./protocols/gamespy3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Unreal Tournament 3';
|
|
@ -1,10 +1,10 @@
|
|||
module.exports = require('./quake3').extend({
|
||||
module.exports = require('./protocols/quake3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Warsow';
|
||||
this.options.port = 44400;
|
||||
},
|
||||
prepState: function(state) {
|
||||
finalizeState: function(state) {
|
||||
this._super(state);
|
||||
if(state.players) {
|
||||
for(var i = 0; i < state.players.length; i++) {
|
|
@ -1,7 +1,8 @@
|
|||
var dgram = require('dgram'),
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
util = require('util'),
|
||||
dns = require('dns');
|
||||
dns = require('dns'),
|
||||
TypeResolver = require('./typeresolver');
|
||||
|
||||
var activeQueries = [];
|
||||
|
||||
|
@ -27,12 +28,16 @@ Gamedig = {
|
|||
query: function(options,callback) {
|
||||
if(callback) options.callback = callback;
|
||||
|
||||
var type = (options.type || '').replace(/\W/g,'');
|
||||
var protocol = require('./protocols/'+type);
|
||||
|
||||
var query = new protocol();
|
||||
var query = TypeResolver(options.type);
|
||||
if(!query) {
|
||||
process.nextTick(function() {
|
||||
callback({error:'Invalid server type: '+options.type});
|
||||
});
|
||||
return;
|
||||
}
|
||||
query.debug = Gamedig.debug;
|
||||
query.udpSocket = udpSocket;
|
||||
query.type = type;
|
||||
query.type = options.type;
|
||||
|
||||
// copy over options
|
||||
for(var i in options) query.options[i] = options[i];
|
|
@ -1,5 +1,16 @@
|
|||
var Iconv = require('iconv-lite'),
|
||||
Bignum = require('bignum');
|
||||
Long = require('long');
|
||||
|
||||
function readUInt64BE(buffer,offset) {
|
||||
var high = buffer.readUInt32BE(offset);
|
||||
var low = buffer.readUInt32BE(offset+4);
|
||||
return new Long(low,high,true);
|
||||
}
|
||||
function readUInt64LE(buffer,offset) {
|
||||
var low = buffer.readUInt32LE(offset);
|
||||
var high = buffer.readUInt32LE(offset+4);
|
||||
return new Long(low,high,true);
|
||||
}
|
||||
|
||||
function Reader(query,buffer) {
|
||||
this.query = query;
|
||||
|
@ -40,7 +51,6 @@ Reader.prototype = {
|
|||
end = start+options.length;
|
||||
if(end > this.buffer.length) return '';
|
||||
this.i = end;
|
||||
if(options.stripnull && this.buffer.readUInt8(end-1) == 0) end--;
|
||||
}
|
||||
|
||||
var out = this.buffer.slice(start, end);
|
||||
|
@ -52,6 +62,22 @@ Reader.prototype = {
|
|||
}
|
||||
return out;
|
||||
},
|
||||
int: function(bytes) {
|
||||
var r = 0;
|
||||
if(this.i+bytes <= this.buffer.length) {
|
||||
if(this.query.byteorder == 'be') {
|
||||
if(bytes == 1) r = this.buffer.readInt8(this.i);
|
||||
else if(bytes == 2) r = this.buffer.readInt16BE(this.i);
|
||||
else if(bytes == 4) r = this.buffer.readInt32BE(this.i);
|
||||
} else {
|
||||
if(bytes == 1) r = this.buffer.readInt8(this.i);
|
||||
else if(bytes == 2) r = this.buffer.readInt16LE(this.i);
|
||||
else if(bytes == 4) r = this.buffer.readInt32LE(this.i);
|
||||
}
|
||||
}
|
||||
this.i += bytes;
|
||||
return r;
|
||||
},
|
||||
uint: function(bytes) {
|
||||
var r = 0;
|
||||
if(this.i+bytes <= this.buffer.length) {
|
||||
|
@ -59,12 +85,12 @@ Reader.prototype = {
|
|||
if(bytes == 1) r = this.buffer.readUInt8(this.i);
|
||||
else if(bytes == 2) r = this.buffer.readUInt16BE(this.i);
|
||||
else if(bytes == 4) r = this.buffer.readUInt32BE(this.i);
|
||||
else if(bytes == 8) r = Bignum.fromBuffer(this.buffer.slice(this.i,this.i+8),{endian:'big',size:'auto'});
|
||||
else if(bytes == 8) r = readUInt64BE(this.buffer,this.i).toString();
|
||||
} else {
|
||||
if(bytes == 1) r = this.buffer.readUInt8(this.i);
|
||||
else if(bytes == 2) r = this.buffer.readUInt16LE(this.i);
|
||||
else if(bytes == 4) r = this.buffer.readUInt32LE(this.i);
|
||||
else if(bytes == 8) r = Bignum.fromBuffer(this.buffer.slice(this.i,this.i+8),{endian:'little',size:'auto'});
|
||||
else if(bytes == 8) r = readUInt64LE(this.buffer,this.i).toString();
|
||||
}
|
||||
}
|
||||
this.i += bytes;
|
||||
|
@ -79,16 +105,6 @@ Reader.prototype = {
|
|||
this.i += 4;
|
||||
return r;
|
||||
},
|
||||
pascal: function(enc) {
|
||||
if(this.i >= this.buffer.length) return '';
|
||||
var length = this.buffer.readUInt8(this.i);
|
||||
this.i++;
|
||||
return this.string({
|
||||
encoding: enc,
|
||||
length: length,
|
||||
stripnull: true
|
||||
});
|
||||
},
|
||||
done: function() {
|
||||
return this.i >= this.buffer.length;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
var Path = require('path'),
|
||||
fs = require('fs');
|
||||
|
||||
var gamesDir = Path.normalize(__dirname+'/../games');
|
||||
|
||||
function readAliases() {
|
||||
var lines = fs.readFileSync(gamesDir+'/aliases.txt','utf8').split('\n');
|
||||
var aliases = {};
|
||||
|
||||
lines.forEach(function(line) {
|
||||
line = line.trim();
|
||||
if(!line) return;
|
||||
if(line.charAt(0) == '#') return;
|
||||
var split = line.split('|');
|
||||
|
||||
aliases[split[0].trim()] = {
|
||||
pretty: split[1].trim(),
|
||||
protocol: split[2].trim(),
|
||||
port: split[3] ? parseInt(split[3]) : 0
|
||||
};
|
||||
});
|
||||
return aliases;
|
||||
}
|
||||
var aliases = readAliases();
|
||||
|
||||
function createQueryInstance(type) {
|
||||
type = Path.basename(type);
|
||||
|
||||
var path = gamesDir+'/'+type;
|
||||
if(type.substr(0,9) == 'protocol-') {
|
||||
path = gamesDir+'/protocols/'+type.substr(9);
|
||||
}
|
||||
|
||||
if(!fs.existsSync(path+'.js')) return false;
|
||||
var protocol = require(path);
|
||||
|
||||
return new protocol();
|
||||
}
|
||||
|
||||
module.exports = function(type) {
|
||||
var alias = aliases[type];
|
||||
|
||||
if(alias) {
|
||||
var query = createQueryInstance('protocol-'+alias.protocol);
|
||||
if(!query) return false;
|
||||
query.pretty = alias.pretty;
|
||||
if(alias.port) query.options.port = alias.port;
|
||||
return query;
|
||||
}
|
||||
return createQueryInstance(type);
|
||||
}
|
19
package.json
19
package.json
|
@ -9,7 +9,7 @@
|
|||
"util",
|
||||
"server"
|
||||
],
|
||||
"main": "index.js",
|
||||
"main": "lib/index.js",
|
||||
"author": "Michael Morrison",
|
||||
"version": "0.1.2",
|
||||
"repository" : {
|
||||
|
@ -26,11 +26,16 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"iconv-lite": ">=0.2.10",
|
||||
"bignum": ">=0.6.1",
|
||||
"async": ">=0.2.9",
|
||||
"compressjs": ">=1.0.0",
|
||||
"gbxremote": "git://github.com/sonicsnes/node-gbxremote.git",
|
||||
"request": ">=2.22.0"
|
||||
"iconv-lite": "~0.2.11",
|
||||
"long": "~1.1.2",
|
||||
"async": "~0.2.10",
|
||||
"compressjs": "~1.0.1",
|
||||
"gbxremote": "~0.1.4",
|
||||
"request": "~2.33.0",
|
||||
"optimist": "~0.6.0",
|
||||
"varint": "~1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"gamedig": "bin/gamedig.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
module.exports = require('./unreal2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Killing Floor';
|
||||
this.options.port = 7708;
|
||||
}
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
module.exports = require('./quake2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Quake 3';
|
||||
this.options.port = 27960;
|
||||
this.sendHeader = 'getstatus';
|
||||
this.responseHeader = 'statusResponse';
|
||||
}
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
module.exports = require('./unreal2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Unreal Tournament 2004';
|
||||
this.options.port = 7778;
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue