mirror of
https://github.com/tribufu/node-gamedig
synced 2026-06-01 09:42:41 +00:00
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
31 changed files with 573 additions and 135 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/node_modules
|
||||||
115
README.md
115
README.md
|
|
@ -1,7 +1,7 @@
|
||||||
node-GameDig - Game Server Query Library
|
node-GameDig - Game Server Query Library
|
||||||
---
|
---
|
||||||
|
|
||||||
Usage
|
Usage from Node.js
|
||||||
---
|
---
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
@ -24,7 +24,7 @@ Gamedig.query(
|
||||||
|
|
||||||
### Input Parameters
|
### Input Parameters
|
||||||
|
|
||||||
* **type**: One of the types from the protocols folder
|
* **type**: One of the game IDs listed in the game list below
|
||||||
* **host**
|
* **host**
|
||||||
* **port**: (optional) Uses the protocol default if not set
|
* **port**: (optional) Uses the protocol default if not set
|
||||||
* **notes**: (optional) Passed through to output
|
* **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
|
Supported Games
|
||||||
---
|
---
|
||||||
|
|
||||||
###Armagetron
|
* Alien Swarm (alienswarm)
|
||||||
|
* Armagetron (armagetron)
|
||||||
###Gamespy 3 Protocol
|
* Build and Shoot (buildandshoot)
|
||||||
* Minecraft
|
* Counter-Strike 1.6 (cs16)
|
||||||
* Unreal Tournament 3
|
* Counter-Strike: Source (css)
|
||||||
|
* Counter-Strike: Global Offensive (csgo)
|
||||||
###GoldSrc Engine
|
* Dino D-Day (dinodday)
|
||||||
* Half Life: Death Match
|
* Garry's Mod (garrysmod)
|
||||||
* Ricochet
|
* The Hidden: Source (hidden)
|
||||||
* Counter-Strike: 1.6
|
* Just Cause Multiplayer (jcmp)
|
||||||
* \+ others
|
* Killing Floor (killingfloor)
|
||||||
|
* KzMod (kzmod)
|
||||||
###Nadeo Protocol
|
* 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
|
Requires additional parameters: login, password
|
||||||
```
|
```
|
||||||
* Trackmania Forever
|
* Starbound (starbound)
|
||||||
* Trackmania 2
|
* Suicide Survival (suicidesurvival)
|
||||||
* Shootmania
|
* Sven Coop (svencoop)
|
||||||
|
* Synergy (synergy)
|
||||||
###Quake 2 Protocol
|
* Team Fortress 2 (tf2)
|
||||||
* Quake 2
|
* Terraria (terraria)
|
||||||
|
|
||||||
###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)
|
|
||||||
```
|
```
|
||||||
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
|
After installing gamedig globally, you can call gamedig via the command line
|
||||||
* Killing Floor
|
using the same parameters mentioned in the API above:
|
||||||
* Unreal Tournament 2004
|
```shell
|
||||||
|
gamedig --type minecraft --host mc.example.com --port 11234
|
||||||
|
```
|
||||||
|
|
||||||
|
The output of the command will be in JSON format.
|
||||||
|
|
|
||||||
27
bin/gamedig.js
Normal file
27
bin/gamedig.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
);
|
||||||
29
games/aliases.txt
Normal file
29
games/aliases.txt
Normal file
|
|
@ -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() {
|
init: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.pretty = 'Armagetron';
|
this.pretty = 'Armagetron';
|
||||||
|
|
@ -18,7 +18,7 @@ module.exports = require('./core').extend({
|
||||||
|
|
||||||
state.raw.port = self.readUInt(reader);
|
state.raw.port = self.readUInt(reader);
|
||||||
state.raw.hostname = self.readString(reader,buffer);
|
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.numplayers = self.readUInt(reader);
|
||||||
state.raw.versionmin = self.readUInt(reader);
|
state.raw.versionmin = self.readUInt(reader);
|
||||||
state.raw.versionmax = self.readUInt(reader);
|
state.raw.versionmax = self.readUInt(reader);
|
||||||
|
|
@ -29,10 +29,12 @@ module.exports = require('./core').extend({
|
||||||
var list = players.split('\n');
|
var list = players.split('\n');
|
||||||
for(var i = 0; i < list.length; i++) {
|
for(var i = 0; i < list.length; i++) {
|
||||||
if(!list[i]) continue;
|
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.uri = self.readString(reader,buffer);
|
||||||
state.raw.globalids = self.readString(reader,buffer);
|
state.raw.globalids = self.readString(reader,buffer);
|
||||||
self.finish(state);
|
self.finish(state);
|
||||||
|
|
@ -56,7 +58,9 @@ module.exports = require('./core').extend({
|
||||||
if(i+2<len) out += String.fromCharCode(hi);
|
if(i+2<len) out += String.fromCharCode(hi);
|
||||||
}
|
}
|
||||||
|
|
||||||
out = out.replace(/0x[0-9a-f]{6}/g,''); // strip color codes
|
|
||||||
return out;
|
return out;
|
||||||
|
},
|
||||||
|
stripColorCodes: function(str) {
|
||||||
|
return str.replace(/0x[0-9a-f]{6}/g,'');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
module.exports = require('./core').extend({
|
module.exports = require('./protocols/core').extend({
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.pretty = 'Build and Shoot';
|
this.pretty = 'Build and Shoot';
|
||||||
30
games/jcmp.js
Normal file
30
games/jcmp.js
Normal file
|
|
@ -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({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
13
games/killingfloor.js
Normal file
13
games/killingfloor.js
Normal file
|
|
@ -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');
|
var dns = require('dns');
|
||||||
|
|
||||||
module.exports = require('./gamespy3').extend({
|
module.exports = require('./protocols/gamespy3').extend({
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.pretty = 'Minecraft';
|
this.pretty = 'Minecraft';
|
||||||
127
games/minecraftping.js
Normal file
127
games/minecraftping.js
Normal file
|
|
@ -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');
|
var request = require('request');
|
||||||
|
|
||||||
module.exports = require('./core').extend({
|
module.exports = require('./protocols/core').extend({
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.pretty = 'Mutant Factions';
|
this.pretty = 'Mutant Factions';
|
||||||
|
|
@ -22,7 +22,7 @@ module.exports = require('./core').extend({
|
||||||
var fields = line.split('::');
|
var fields = line.split('::');
|
||||||
var ip = fields[2];
|
var ip = fields[2];
|
||||||
var port = fields[3];
|
var port = fields[3];
|
||||||
if(ip == this.options.address && port == this.options.port) {
|
if(ip == self.options.address && port == self.options.port) {
|
||||||
found = fields;
|
found = fields;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
var EventEmitter = require('events').EventEmitter,
|
var EventEmitter = require('events').EventEmitter,
|
||||||
dns = require('dns'),
|
dns = require('dns'),
|
||||||
async = require('async'),
|
async = require('async'),
|
||||||
Class = require('../Class'),
|
Class = require('../../lib/Class'),
|
||||||
Reader = require('../reader');
|
Reader = require('../../lib/reader');
|
||||||
|
|
||||||
module.exports = Class.extend(EventEmitter,{
|
module.exports = Class.extend(EventEmitter,{
|
||||||
init: function() {
|
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(!('address' in this.options)) return this.fatal('Attempted to send without setting an address');
|
||||||
|
|
||||||
if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary');
|
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);
|
this.udpSocket.send(buffer,0,buffer.length,this.options.port,this.options.address);
|
||||||
},
|
},
|
||||||
_udpResponse: function(buffer) {
|
_udpResponse: function(buffer) {
|
||||||
|
|
@ -21,28 +21,48 @@ module.exports = require('./core').extend({
|
||||||
var key = reader.string();
|
var key = reader.string();
|
||||||
if(!key) break;
|
if(!key) break;
|
||||||
var value = reader.string();
|
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;
|
state.raw[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mode = '';
|
|
||||||
while(!reader.done()) {
|
while(!reader.done()) {
|
||||||
var mode = reader.string();
|
var mode = reader.string();
|
||||||
|
if(mode.charCodeAt(0) <= 2) mode = mode.substring(1);
|
||||||
|
if(!mode) continue;
|
||||||
|
var offset = 0;
|
||||||
reader.skip(1);
|
reader.skip(1);
|
||||||
|
|
||||||
while(!reader.done()) {
|
while(!reader.done()) {
|
||||||
var item = reader.string();
|
var item = reader.string();
|
||||||
if(!item) break;
|
if(!item) break;
|
||||||
|
|
||||||
if(mode.substr(-1) == '_') {
|
if(
|
||||||
// players
|
mode == 'player_'
|
||||||
state.players.push({name:item})
|
|| 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('hostname' in state.raw) state.name = state.raw.hostname;
|
||||||
if('map' in state.raw) state.map = state.raw.map;
|
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);
|
self.finish(state);
|
||||||
|
|
||||||
|
|
@ -79,7 +99,7 @@ module.exports = require('./core').extend({
|
||||||
var id = buffer.readUInt16LE(14);
|
var id = buffer.readUInt16LE(14);
|
||||||
var last = (id & 0x80);
|
var last = (id & 0x80);
|
||||||
id = id & 0x7f;
|
id = id & 0x7f;
|
||||||
if(last) numPackets = id+1;
|
if(last || self._singlePacketSplits) numPackets = id+1;
|
||||||
|
|
||||||
packets[id] = buffer.slice(16);
|
packets[id] = buffer.slice(16);
|
||||||
|
|
||||||
21
games/protocols/quake3.js
Normal file
21
games/protocols/quake3.js
Normal file
|
|
@ -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) {
|
self.sendPacket(0,true,function(b) {
|
||||||
var reader = self.reader(b);
|
var reader = self.reader(b);
|
||||||
state.raw.serverid = reader.uint(4);
|
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.port = reader.uint(4);
|
||||||
state.raw.queryport = reader.uint(4);
|
state.raw.queryport = reader.uint(4);
|
||||||
state.name = reader.pascal();
|
state.name = self.readUnrealString(reader,true);
|
||||||
state.map = reader.pascal();
|
self.readUnrealString(reader); // unknown?
|
||||||
state.raw.gametype = reader.pascal();
|
state.map = self.readUnrealString(reader,true);
|
||||||
state.raw.numplayers = reader.uint(4);
|
state.raw.gametype = self.readUnrealString(reader,true);
|
||||||
state.maxplayers = reader.uint(4);
|
self.readExtraInfo(reader,state);
|
||||||
state.raw.ping = reader.uint(4);
|
|
||||||
c();
|
c();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -32,8 +32,8 @@ module.exports = require('./core').extend({
|
||||||
state.raw.mutators = [];
|
state.raw.mutators = [];
|
||||||
state.raw.rules = {};
|
state.raw.rules = {};
|
||||||
while(!reader.done()) {
|
while(!reader.done()) {
|
||||||
var key = reader.pascal();
|
var key = self.readUnrealString(reader,true);
|
||||||
var value = reader.pascal();
|
var value = self.readUnrealString(reader,true);
|
||||||
if(key == 'Mutator') state.raw.mutators.push(value);
|
if(key == 'Mutator') state.raw.mutators.push(value);
|
||||||
else state.raw.rules[key] = value;
|
else state.raw.rules[key] = value;
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ module.exports = require('./core').extend({
|
||||||
var reader = self.reader(b);
|
var reader = self.reader(b);
|
||||||
while(!reader.done()) {
|
while(!reader.done()) {
|
||||||
var id = reader.uint(4);
|
var id = reader.uint(4);
|
||||||
var name = reader.pascal();
|
var name = self.readUnrealString(reader,true);
|
||||||
var ping = reader.uint(4);
|
var ping = reader.uint(4);
|
||||||
var score = reader.uint(4);
|
var score = reader.uint(4);
|
||||||
reader.skip(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) {
|
sendPacket: function(type,required,callback) {
|
||||||
var outbuffer = new Buffer([0x79,0,0,0,type]);
|
var outbuffer = new Buffer([0x79,0,0,0,type]);
|
||||||
|
|
||||||
|
|
@ -6,6 +6,10 @@ module.exports = require('./core').extend({
|
||||||
this._super();
|
this._super();
|
||||||
this.goldsrc = false;
|
this.goldsrc = false;
|
||||||
this.options.port = 27015;
|
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) {
|
run: function(state) {
|
||||||
|
|
||||||
|
|
@ -34,9 +38,14 @@ module.exports = require('./core').extend({
|
||||||
if(self.goldsrc) state.raw.protocol = reader.uint(1);
|
if(self.goldsrc) state.raw.protocol = reader.uint(1);
|
||||||
else state.raw.numbots = reader.uint(1);
|
else state.raw.numbots = reader.uint(1);
|
||||||
|
|
||||||
state.raw.listentype = String.fromCharCode(reader.uint(1));
|
state.raw.listentype = reader.uint(1);
|
||||||
state.raw.environment = String.fromCharCode(reader.uint(1));
|
state.raw.environment = reader.uint(1);
|
||||||
state.password = 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) {
|
if(self.goldsrc) {
|
||||||
state.raw.ismod = reader.uint(1);
|
state.raw.ismod = reader.uint(1);
|
||||||
if(state.raw.ismod) {
|
if(state.raw.ismod) {
|
||||||
|
|
@ -71,12 +80,16 @@ module.exports = require('./core').extend({
|
||||||
if(extraFlag & 0x01) state.raw.gameid = reader.uint(8);
|
if(extraFlag & 0x01) state.raw.gameid = reader.uint(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(state.raw.protocol == 7 && state.raw.steamappid == 215) {
|
||||||
|
self._skipSizeInSplitHeader = true;
|
||||||
|
}
|
||||||
|
|
||||||
c();
|
c();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function(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);
|
var reader = self.reader(b);
|
||||||
challenge = reader.uint(4);
|
challenge = reader.uint(4);
|
||||||
c();
|
c();
|
||||||
|
|
@ -89,7 +102,7 @@ module.exports = require('./core').extend({
|
||||||
for(var i = 0; i < num; i++) {
|
for(var i = 0; i < num; i++) {
|
||||||
reader.skip(1);
|
reader.skip(1);
|
||||||
var name = reader.string();
|
var name = reader.string();
|
||||||
var score = reader.uint(4);
|
var score = reader.int(4);
|
||||||
var time = reader.float();
|
var time = reader.float();
|
||||||
|
|
||||||
// connecting players don't could as players.
|
// connecting players don't could as players.
|
||||||
|
|
@ -131,6 +144,12 @@ module.exports = require('./core').extend({
|
||||||
state.raw.rules[key] = value;
|
state.raw.rules[key] = value;
|
||||||
}
|
}
|
||||||
c();
|
c();
|
||||||
|
}, function() {
|
||||||
|
// no rules were returned after timeout --
|
||||||
|
// the server probably has them disabled
|
||||||
|
// ignore the timeout
|
||||||
|
c();
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function(c) {
|
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 self = this;
|
||||||
|
|
||||||
var challengeLength = challenge === false ? 0 : 4;
|
var challengeLength = challenge === false ? 0 : 4;
|
||||||
|
|
@ -152,6 +171,7 @@ module.exports = require('./core').extend({
|
||||||
|
|
||||||
function received(payload) {
|
function received(payload) {
|
||||||
var type = payload.readUInt8(0);
|
var type = payload.readUInt8(0);
|
||||||
|
if(self.debug) console.log("Received "+type+" expected "+expect);
|
||||||
if(type != expect) return;
|
if(type != expect) return;
|
||||||
callback(payload.slice(1));
|
callback(payload.slice(1));
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -175,17 +195,23 @@ module.exports = require('./core').extend({
|
||||||
if(self.goldsrc) {
|
if(self.goldsrc) {
|
||||||
id = buffer.readUInt8(8);
|
id = buffer.readUInt8(8);
|
||||||
numPackets = id & 0x0f;
|
numPackets = id & 0x0f;
|
||||||
id = id & 0xf0 >> 4;
|
id = (id & 0xf0) >> 4;
|
||||||
payload = buffer.slice(9);
|
payload = buffer.slice(9);
|
||||||
} else {
|
} else {
|
||||||
numPackets = buffer.readUInt8(8);
|
numPackets = buffer.readUInt8(8);
|
||||||
id = buffer.readUInt8(9);
|
id = buffer.readUInt8(9);
|
||||||
if(id == 0 && bzip) payload = buffer.slice(20);
|
var sizeOffset = self._skipSizeInSplitHeader ? 0 : 2;
|
||||||
else payload = buffer.slice(12);
|
if(id == 0 && bzip) payload = buffer.slice(18+sizeOffset);
|
||||||
|
else payload = buffer.slice(10+sizeOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
packets[id] = payload;
|
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;
|
if(!numPackets || Object.keys(packets).length != numPackets) return;
|
||||||
|
|
||||||
// assemble the parts
|
// assemble the parts
|
||||||
|
|
@ -198,11 +224,10 @@ module.exports = require('./core').extend({
|
||||||
list.push(packets[i]);
|
list.push(packets[i]);
|
||||||
}
|
}
|
||||||
var assembled = Buffer.concat(list);
|
var assembled = Buffer.concat(list);
|
||||||
var payload = assembled.slice(4);
|
if(bzip) assembled = new Buffer(Bzip2.decompressFile(assembled));
|
||||||
if(bzip) payload = Bzip2.uncompressFile(payload);
|
|
||||||
|
|
||||||
return received(payload);
|
return received(assembled.slice(4));
|
||||||
}
|
}
|
||||||
});
|
},ontimeout);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
6
games/protocols/valvegold.js
Normal file
6
games/protocols/valvegold.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = require('./valve').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.goldsrc = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
module.exports = require('./core').extend({
|
module.exports = require('./protocols/core').extend({
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.pretty = 'Terraria';
|
this.pretty = 'Terraria';
|
||||||
12
games/ut2004.js
Normal file
12
games/ut2004.js
Normal file
|
|
@ -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() {
|
init: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.pretty = 'Unreal Tournament 3';
|
this.pretty = 'Unreal Tournament 3';
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
module.exports = require('./quake3').extend({
|
module.exports = require('./protocols/quake3').extend({
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.pretty = 'Warsow';
|
this.pretty = 'Warsow';
|
||||||
this.options.port = 44400;
|
this.options.port = 44400;
|
||||||
},
|
},
|
||||||
prepState: function(state) {
|
finalizeState: function(state) {
|
||||||
this._super(state);
|
this._super(state);
|
||||||
if(state.players) {
|
if(state.players) {
|
||||||
for(var i = 0; i < state.players.length; i++) {
|
for(var i = 0; i < state.players.length; i++) {
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
var dgram = require('dgram'),
|
var dgram = require('dgram'),
|
||||||
EventEmitter = require('events').EventEmitter,
|
EventEmitter = require('events').EventEmitter,
|
||||||
util = require('util'),
|
util = require('util'),
|
||||||
dns = require('dns');
|
dns = require('dns'),
|
||||||
|
TypeResolver = require('./typeresolver');
|
||||||
|
|
||||||
var activeQueries = [];
|
var activeQueries = [];
|
||||||
|
|
||||||
|
|
@ -27,12 +28,16 @@ Gamedig = {
|
||||||
query: function(options,callback) {
|
query: function(options,callback) {
|
||||||
if(callback) options.callback = callback;
|
if(callback) options.callback = callback;
|
||||||
|
|
||||||
var type = (options.type || '').replace(/\W/g,'');
|
var query = TypeResolver(options.type);
|
||||||
var protocol = require('./protocols/'+type);
|
if(!query) {
|
||||||
|
process.nextTick(function() {
|
||||||
var query = new protocol();
|
callback({error:'Invalid server type: '+options.type});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
query.debug = Gamedig.debug;
|
||||||
query.udpSocket = udpSocket;
|
query.udpSocket = udpSocket;
|
||||||
query.type = type;
|
query.type = options.type;
|
||||||
|
|
||||||
// copy over options
|
// copy over options
|
||||||
for(var i in options) query.options[i] = options[i];
|
for(var i in options) query.options[i] = options[i];
|
||||||
|
|
@ -1,5 +1,16 @@
|
||||||
var Iconv = require('iconv-lite'),
|
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) {
|
function Reader(query,buffer) {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
|
|
@ -40,7 +51,6 @@ Reader.prototype = {
|
||||||
end = start+options.length;
|
end = start+options.length;
|
||||||
if(end > this.buffer.length) return '';
|
if(end > this.buffer.length) return '';
|
||||||
this.i = end;
|
this.i = end;
|
||||||
if(options.stripnull && this.buffer.readUInt8(end-1) == 0) end--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var out = this.buffer.slice(start, end);
|
var out = this.buffer.slice(start, end);
|
||||||
|
|
@ -52,6 +62,22 @@ Reader.prototype = {
|
||||||
}
|
}
|
||||||
return out;
|
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) {
|
uint: function(bytes) {
|
||||||
var r = 0;
|
var r = 0;
|
||||||
if(this.i+bytes <= this.buffer.length) {
|
if(this.i+bytes <= this.buffer.length) {
|
||||||
|
|
@ -59,12 +85,12 @@ Reader.prototype = {
|
||||||
if(bytes == 1) r = this.buffer.readUInt8(this.i);
|
if(bytes == 1) r = this.buffer.readUInt8(this.i);
|
||||||
else if(bytes == 2) r = this.buffer.readUInt16BE(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 == 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 {
|
} else {
|
||||||
if(bytes == 1) r = this.buffer.readUInt8(this.i);
|
if(bytes == 1) r = this.buffer.readUInt8(this.i);
|
||||||
else if(bytes == 2) r = this.buffer.readUInt16LE(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 == 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;
|
this.i += bytes;
|
||||||
|
|
@ -79,16 +105,6 @@ Reader.prototype = {
|
||||||
this.i += 4;
|
this.i += 4;
|
||||||
return r;
|
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() {
|
done: function() {
|
||||||
return this.i >= this.buffer.length;
|
return this.i >= this.buffer.length;
|
||||||
}
|
}
|
||||||
51
lib/typeresolver.js
Normal file
51
lib/typeresolver.js
Normal file
|
|
@ -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",
|
"util",
|
||||||
"server"
|
"server"
|
||||||
],
|
],
|
||||||
"main": "index.js",
|
"main": "lib/index.js",
|
||||||
"author": "Michael Morrison",
|
"author": "Michael Morrison",
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"repository" : {
|
"repository" : {
|
||||||
|
|
@ -26,11 +26,16 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iconv-lite": ">=0.2.10",
|
"iconv-lite": "~0.2.11",
|
||||||
"bignum": ">=0.6.1",
|
"long": "~1.1.2",
|
||||||
"async": ">=0.2.9",
|
"async": "~0.2.10",
|
||||||
"compressjs": ">=1.0.0",
|
"compressjs": "~1.0.1",
|
||||||
"gbxremote": "git://github.com/sonicsnes/node-gbxremote.git",
|
"gbxremote": "~0.1.4",
|
||||||
"request": ">=2.22.0"
|
"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…
Add table
Add a link
Reference in a new issue