mirror of
https://github.com/gamedig/node-gamedig.git
synced 2024-11-17 09:18:31 +01:00
Super epic commit 2
Added pretty much every game ever Tons of new protocols and game definitions Cleaned up and discovered some new tricks in gamespy3 and quake2
This commit is contained in:
parent
b51877ef5c
commit
e23aa6cf9c
21 changed files with 854 additions and 222 deletions
62
README.md
62
README.md
|
@ -61,6 +61,7 @@ Supported Games
|
|||
|
||||
* Age of Chivalry (ageofchivalry)
|
||||
* Alien Swarm (alienswarm)
|
||||
* Aliens vs Predator 2 (avp2)
|
||||
* America's Army 1 (americasarmy) [[Separate Query Port - Usually port+1](#separate-query-port)]
|
||||
* America's Army 2 (americasarmy2) [[Separate Query Port - Usually port+1](#separate-query-port)]
|
||||
* America's Army 3 (americasarmy3) [[Separate Query Port - Usually 27020](#separate-query-port)]
|
||||
|
@ -71,42 +72,99 @@ Supported Games
|
|||
* Armagetron (armagetron)
|
||||
* Battlefield 1942 (bf1942) [[Separate Query Port - Usually 23000](#separate-query-port)]
|
||||
* Battlefield 2 (bf2) [[Separate Query Port - Usually 29900](#separate-query-port)]
|
||||
* Battlefield 3 (bf3) [[Separate Query Port - Usually port+22000](#separate-query-port)]
|
||||
* Battlefield 4 (bf4) [[Separate Query Port - Usually port+22000](#separate-query-port)]
|
||||
* Battlefield: Bad Company 2 (bfbc2) [[Separate Query Port - Usually 48888](#separate-query-port)]
|
||||
* Battlefield: Vietnam (bfv) [[Separate Query Port - Usually 48888](#separate-query-port)]
|
||||
* Brink (brink) [[Separate Query Port - Usually port+1](#separate-query-port)]
|
||||
* Build and Shoot (buildandshoot)
|
||||
* Call of Duty (cod)
|
||||
* Call of Duty 2 (cod2)
|
||||
* Call of Duty 4 (cod4)
|
||||
* Call of Duty: Modern Warfare 3 (codmw3) [[Separate Query Port - Usually port+2](#separate-query-port)]
|
||||
* Call of Duty: United Offensive (coduo)
|
||||
* Call of Duty: World at War (codwaw)
|
||||
* Counter-Strike 1.6 (cs16)
|
||||
* Counter-Strike: Condition Zero (cscz)
|
||||
* Counter-Strike: Source (css)
|
||||
* Counter-Strike: Global Offensive (csgo)
|
||||
* Crysis (crysis)
|
||||
* Crysis 2 (crysis2)
|
||||
* Crysis Wars (crysiswars)
|
||||
* Darkest Hour (darkesthour) [[Separate Query Port - Usually port+1](#separate-query-port)]
|
||||
* Day of Defeat (dod)
|
||||
* Day of Defeat: Source (dods)
|
||||
* DayZ (dayz) [[Separate Query Port - Usually 27016-27020](#separate-query-port)]
|
||||
* Dino D-Day (dinodday)
|
||||
* Doom 3 (doom3)
|
||||
* DOTA 2 (dota2)
|
||||
* Enemy Territory Quake Wars (etqw)
|
||||
* F.E.A.R. (fear)
|
||||
* Fortress Forever (fortressforever)
|
||||
* Frontlines: Fuel of War (ffow)
|
||||
* Garry's Mod (garrysmod)
|
||||
* Ghost Recon: Advanced Warfighter (graw)
|
||||
* Ghost Recon: Advanced Warfighter 2 (graw2)
|
||||
* Gore (gore)
|
||||
* Half-Life 1 Deathmatch (hldm)
|
||||
* Half-Life 2 Deathmatch (hl2dm)
|
||||
* Halo (halo)
|
||||
* The Hidden: Source (hidden)
|
||||
* Just Cause Multiplayer (jcmp)
|
||||
* Homefront (homefront)
|
||||
* Insurgency (insurgency)
|
||||
* Just Cause 2 Multiplayer (jc2mp)
|
||||
* Killing Floor (killingfloor)
|
||||
* KzMod (kzmod)
|
||||
* Left 4 Dead (left4dead)
|
||||
* Left 4 Dead 2 (left4dead2)
|
||||
* Mafia 2 Multiplayer (m2mp) [[Separate Query Port - Usually port+1](#separate-query-port)]
|
||||
* Medal of Honor: Allied Assault (mohaa) [[Separate Query Port - Usually port+97](#separate-query-port)]
|
||||
* Medal of Honor: Spearhead (mohsh) [[Separate Query Port - Usually port+97](#separate-query-port)]
|
||||
* Medal of Honor: Breakthrough (mohbt) [[Separate Query Port - Usually port+97](#separate-query-port)]
|
||||
* Medal of Honor 2010 (moh2010) [[Separate Query Port - Usually 48888](#separate-query-port)]
|
||||
* Medal of Honor: Warfighter (mohwf)
|
||||
* Minecraft (minecraft) [[Additional Notes](#minecraft)]
|
||||
* Monday Night Combat (mnc)
|
||||
* Multi Theft Auto [[Separate Query Port - Usually port+123](#separate-query-port)]
|
||||
* Mutant Factions (mutantfactions)
|
||||
* Natural Selection (ns)
|
||||
* Natural Selection 2 (ns2) [[Separate Query Port - Usually port+1](#separate-query-port)]
|
||||
* No More Room in Hell (nmrih)
|
||||
* Nuclear Dawn (nucleardawn)
|
||||
* Prey (prey)
|
||||
* Quake 1 (quake1)
|
||||
* Quake 2 (quake2)
|
||||
* Quake 3 (quake3)
|
||||
* Quake 4 (quake4)
|
||||
* Red Orchestra: Ostfront 41-45 (redorchestraost) [[Separate Query Port - Usually port+10](#separate-query-port)]
|
||||
* Red Orchestra 2 (redorchestra2) [[Separate Query Port - Usually 27015](#separate-query-port)]
|
||||
* Return to Castle Wolfenstein (rtcw)
|
||||
* Ricochet (ricochet)
|
||||
* Rust (rust)
|
||||
* The Ship (ship)
|
||||
* ShootMania (shootmania) [[Additional Notes](#nadeo-shootmania--trackmania--etc)]
|
||||
* Soldier of Fortune 2 (sof2)
|
||||
* Star Wars: Battlefront 2 (swbf2)
|
||||
* Star Wars: Jedi Knight (swjk)
|
||||
* Star Wars: Jedi Knight 2 (swjk2)
|
||||
* Starbound (starbound)
|
||||
* Suicide Survival (suicidesurvival)
|
||||
* SWAT 4 (swat4) [[Separate Query Port - Usually port+2](#separate-query-port)]
|
||||
* Sven Coop (svencoop)
|
||||
* Synergy (synergy)
|
||||
* Team Fortress 2 (tf2)
|
||||
* Team Fortress Classic (tfc)
|
||||
* Terraria (terraria) [[Additional Notes](#terraria)]
|
||||
* TrackMania 2 (trackmania2) [[Additional Notes](#nadeo-shootmania--trackmania--etc)]
|
||||
* TrackMania Forever (trackmaniaforever) [[Additional Notes](#nadeo-shootmania--trackmania--etc)]
|
||||
* Unreal Tournament (ut) [[Separate Query Port - Usually port+1](#separate-query-port)]
|
||||
* Unreal Tournament 2004 (ut2004)
|
||||
* Unreal Tournament 3 (ut3)
|
||||
* Unreal Tournament 3 (ut3) [[Separate Query Port - Usually 6500](#separate-query-port)]
|
||||
* Warsow (warsow)
|
||||
* Wolfenstein 2009 (wolfenstein2009)
|
||||
* Wolfenstein: Enemy Territory (wolfensteinet)
|
||||
* Zombie Master (zombiemaster)
|
||||
* Zombie Panic: Source (zps)
|
||||
|
||||
Don't see your game listed here?
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
ageofchivalry|Age of Chivalry|valve
|
||||
alienswarm|Alien Swarm|valve
|
||||
avp2|Aliens vs Predator 2|gamespy1|27888
|
||||
americasarmy2|America's Army 2|americasarmy
|
||||
americasarmy3|America's Army 3|valve|27020
|
||||
americasarmypg|America's Army: Proving Grounds|valve|27020
|
||||
|
@ -10,30 +11,80 @@ arma2|ArmA Armed Assault 2|gamespy3|2302
|
|||
arma3|ArmA Armed Assault 3|gamespy3|2302
|
||||
bf1942|Battlefield 1942|gamespy1|23000
|
||||
bf2142|Battlefield 2142|gamespy3|29900
|
||||
bf3|Battlefield 3|battlefield
|
||||
bf4|Battlefield 4|battlefield
|
||||
bfv|Battlefield Vietnam|gamespy2|23000
|
||||
brink|Brink|valve|27016
|
||||
cod|Call of Duty|quake3|28960
|
||||
cod2|Call of Duty 2|quake3|28960
|
||||
cod4|Call of Duty 4|quake3|28960
|
||||
codmw3|Call of Duty: Modern Warfare 3|valve|27017
|
||||
coduo|Call of Duty: United Offensive|quake3|28960
|
||||
codwaw|Call of Duty: World at War|quake3|28960
|
||||
csgo|Counter-Strike: Global Offensive|valve
|
||||
css|Counter-Strike: Source|valve
|
||||
cs16|Counter-Strike 1.6|valvegold
|
||||
cs16|Counter-Strike 1.6|valve
|
||||
cscz|Counter-Strike: Condition Zero|valve
|
||||
crysis|Crysis|gamespy3|64087
|
||||
crysis2|Crysis 2|gamespy3|64000
|
||||
crysiswars|Crysis Wars|gamespy3|64100
|
||||
darkesthour|Darkest Hour|unreal2|7758
|
||||
dayz|DayZ|valve|2302
|
||||
dinodday|Dino D-Day|valve
|
||||
dod|Day of Defeat|valve
|
||||
dods|Day of Defeat: Source|valve
|
||||
doom3|Doom 3|doom3
|
||||
dota2|DOTA 2|valve
|
||||
fear|F.E.A.R.|gamespy2|27888
|
||||
fortressforever|Fortress Forever|valve
|
||||
garrysmod|Garry's Mod|valve
|
||||
graw|Ghost Recon: Advanced Warfighter|gamespy2
|
||||
graw2|Ghost Recon: Advanced Warfighter 2|gamespy2
|
||||
gore|Gore|gamespy1
|
||||
hldm|Half-Life 1 Deathmatch|valve
|
||||
hl2dm|Half-Life 2 Deathmatch|valve
|
||||
halo|Halo|gamespy2|2302
|
||||
hidden|The Hidden: Source|valve
|
||||
homefront|Homefront|valve
|
||||
insurgency|Insurgency|valve
|
||||
kzmod|KzMod|valve
|
||||
left4dead|Left 4 Dead|valve
|
||||
left4dead2|Left 4 Dead 2|valve
|
||||
mohaa|Medal of Honor: Allied Assault|gamespy1|12300
|
||||
mohsh|Medal of Honor: Spearhead|gamespy1|12300
|
||||
mohbt|Medal of Honor: Breakthrough|gamespy1|12300
|
||||
moh2010|Medal of Honor 2010|battlefield|48888
|
||||
mohwf|Medal of Honor: Warfighter|battlefield|25200
|
||||
mnc|Monday Night Combat|valve
|
||||
mta|Multi Theft Auto|ase|22126
|
||||
nmrih|No More Room in Hell|valve
|
||||
ns|Natural Selection|valvegold
|
||||
ns|Natural Selection|valve
|
||||
ns2|Natural Selection 2|valve|27016
|
||||
nucleardawn|Nuclear Dawn|valve
|
||||
prey|Prey|doom3|27719
|
||||
quake2|Quake 2|quake2
|
||||
quake3|Quake 3|quake3
|
||||
ricochet|Ricochet|valvegold
|
||||
redorchestraost|Red Orchestra: Ostfront 41-45|gamespy1|7767
|
||||
redorchestra2|Red Orchestra 2|valve
|
||||
rtcw|Return to Castle Wolfenstein|quake3|27960
|
||||
ricochet|Ricochet|valve
|
||||
rust|Rust|valve|28016
|
||||
ship|The Ship|valve
|
||||
shootmania|Shootmania|nadeo
|
||||
sof2|Soldier of Fortune 2|quake3|20100
|
||||
swbf2|Star Wars: Battlefront 2|gamespy2|3658
|
||||
swjk|Star Wars: Jedi Knight|quake3|29070
|
||||
swjk2|Star Wars: Jedi Knight 2|quake3|28070
|
||||
starbound|Starbound|valve|21025
|
||||
suicidesurvival|Suicide Survival|valve
|
||||
svencoop|Sven Coop|valvegold
|
||||
swat4|SWAT 4|gamespy2|10482
|
||||
svencoop|Sven Coop|valve
|
||||
synergy|Synergy|valve
|
||||
tfc|Team Fortress Classic|valve
|
||||
tf2|Team Fortress 2|valve
|
||||
trackmania2|Trackmania 2|nadeo
|
||||
trackmaniaforever|Trackmania Forever|nadeo
|
||||
ut|Unreal Tournament|gamespy1|7778
|
||||
wolfensteinet|Wolfenstein: Enemy Territory|quake3|27960
|
||||
zombiemaster|Zombie Master|valve
|
||||
zps|Zombie Panic: Source|valve
|
||||
|
|
8
games/bfbc2.js
Normal file
8
games/bfbc2.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = require('./protocols/battlefield').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Battlefield: Bad Company 2';
|
||||
this.options.port = 48888;
|
||||
this.isBadCompany2 = true;
|
||||
}
|
||||
});
|
11
games/etqw.js
Normal file
11
games/etqw.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = require('./protocols/doom3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Enemy Territory Quake Wars';
|
||||
this.options.port = 27733;
|
||||
this.isEtqw = true;
|
||||
this.hasSpaceBeforeClanTag = true;
|
||||
this.hasClanTag = true;
|
||||
this.hasTypeFlag = true;
|
||||
}
|
||||
});
|
35
games/ffow.js
Normal file
35
games/ffow.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
module.exports = require('./protocols/valve').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Frontlines: Fuel of War';
|
||||
this.options.port = 5478;
|
||||
this.byteorder = 'be';
|
||||
this.legacyChallenge = true;
|
||||
},
|
||||
queryInfo: function(state,c) {
|
||||
var self = this;
|
||||
self.sendPacket(0x46,false,new Buffer('LSQ'),0x49,function(b) {
|
||||
var reader = self.reader(b);
|
||||
|
||||
state.raw.protocol = reader.uint(1);
|
||||
state.name = reader.string();
|
||||
state.map = reader.string();
|
||||
state.raw.mod = reader.string();
|
||||
state.raw.gamemode = reader.string();
|
||||
state.raw.description = reader.string();
|
||||
state.raw.version = reader.string();
|
||||
state.raw.port = reader.uint(2);
|
||||
state.raw.numplayers = reader.uint(1);
|
||||
state.maxplayers = 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.secure = reader.uint(1);
|
||||
state.raw.averagefps = reader.uint(1);
|
||||
state.raw.round = reader.uint(1);
|
||||
state.raw.maxrounds = reader.uint(1);
|
||||
state.raw.timeleft = reader.uint(2);
|
||||
c();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -15,7 +15,7 @@ module.exports = require('./protocols/gamespy3').extend({
|
|||
this._super();
|
||||
this.options.port = 7777;
|
||||
this.pretty = 'Just Cause 2 Multiplayer';
|
||||
this._singlePacketSplits = true;
|
||||
this.useOnlySingleSplit = true;
|
||||
},
|
||||
finalizeState: function(state) {
|
||||
this._super(state);
|
39
games/m2mp.js
Normal file
39
games/m2mp.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.encoding = 'latin1';
|
||||
this.pretty = 'Mafia 2 Multiplayer';
|
||||
this.options.port = 27016;
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
|
||||
this.udpSend('M2MP',function(buffer) {
|
||||
var reader = self.reader(buffer);
|
||||
|
||||
var header = reader.string({length:4});
|
||||
if(header != 'M2MP') return;
|
||||
|
||||
state.name = self.readString(reader);
|
||||
state.raw.numplayers = self.readString(reader);
|
||||
state.maxplayers = self.readString(reader);
|
||||
state.raw.gamemode = self.readString(reader);
|
||||
state.password = !!reader.uint(1);
|
||||
|
||||
while(!reader.done()) {
|
||||
var name = self.readString(reader);
|
||||
if(!name) break;
|
||||
state.players.push({
|
||||
name:name
|
||||
});
|
||||
}
|
||||
|
||||
self.finish(state);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
readString: function(reader) {
|
||||
var length = reader.uint(1);
|
||||
return reader.string({length:length-1});
|
||||
},
|
||||
});
|
|
@ -4,6 +4,15 @@ var varint = require('varint'),
|
|||
function varIntBuffer(num) {
|
||||
return new Buffer(varint.encode(num));
|
||||
}
|
||||
function buildPacket(id,data) {
|
||||
if(!data) data = new Buffer(0);
|
||||
var idBuffer = varIntBuffer(id);
|
||||
return Buffer.concat([
|
||||
varIntBuffer(data.length+idBuffer.length),
|
||||
idBuffer,
|
||||
data
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
|
@ -32,22 +41,12 @@ module.exports = require('./protocols/core').extend({
|
|||
portBuf,
|
||||
varIntBuffer(1)
|
||||
];
|
||||
|
||||
function buildPacket(id,data) {
|
||||
if(!data) data = new Buffer(0);
|
||||
var idBuffer = varIntBuffer(id);
|
||||
return Buffer.concat([
|
||||
varIntBuffer(data.length+idBuffer.length),
|
||||
idBuffer,
|
||||
data
|
||||
]);
|
||||
}
|
||||
|
||||
var outBuffer = Buffer.concat([
|
||||
buildPacket(0,Buffer.concat(bufs)),
|
||||
buildPacket(0)
|
||||
]);
|
||||
|
||||
|
||||
self.tcpSend(outBuffer, function(data) {
|
||||
if(data.length < 10) return false;
|
||||
var expected = varint.decode(data);
|
||||
|
@ -55,6 +54,7 @@ module.exports = require('./protocols/core').extend({
|
|||
if(data.length < expected) return false;
|
||||
receivedData = data;
|
||||
c();
|
||||
return true;
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
|
|
50
games/protocols/ase.js
Normal file
50
games/protocols/ase.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
module.exports = require('./core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
self.udpSend('s',function(buffer) {
|
||||
var reader = self.reader(buffer);
|
||||
|
||||
var header = reader.string({length:4});
|
||||
if(header != 'EYE1') return;
|
||||
|
||||
state.raw.gamename = self.readString(reader);
|
||||
state.raw.port = parseInt(self.readString(reader));
|
||||
state.name = self.readString(reader);
|
||||
state.raw.gametype = self.readString(reader);
|
||||
state.map = self.readString(reader);
|
||||
state.raw.version = self.readString(reader);
|
||||
state.password = self.readString(reader) == '1';
|
||||
state.raw.numplayers = parseInt(self.readString(reader));
|
||||
state.maxplayers = parseInt(self.readString(reader));
|
||||
|
||||
while(!reader.done()) {
|
||||
var key = self.readString(reader);
|
||||
if(!key) break;
|
||||
var value = self.readString(reader);
|
||||
state.raw[key] = value;
|
||||
}
|
||||
|
||||
console.log(reader.rest());
|
||||
while(!reader.done()) {
|
||||
var flags = reader.uint(1);
|
||||
var player = {};
|
||||
if(flags & 1) player.name = self.readString(reader);
|
||||
if(flags & 2) player.team = self.readString(reader);
|
||||
if(flags & 4) player.skin = self.readString(reader);
|
||||
if(flags & 8) player.score = parseInt(self.readString(reader));
|
||||
if(flags & 16) player.ping = parseInt(self.readString(reader));
|
||||
if(flags & 32) player.time = parseInt(self.readString(reader));
|
||||
state.players.push(player);
|
||||
}
|
||||
|
||||
self.finish(state);
|
||||
});
|
||||
},
|
||||
readString: function(reader) {
|
||||
var len = reader.uint(1);
|
||||
return reader.string({length:len-1});
|
||||
}
|
||||
});
|
164
games/protocols/battlefield.js
Normal file
164
games/protocols/battlefield.js
Normal file
|
@ -0,0 +1,164 @@
|
|||
var async = require('async');
|
||||
|
||||
function buildPacket(params) {
|
||||
var self = this;
|
||||
|
||||
var paramBuffers = [];
|
||||
params.forEach(function(param) {
|
||||
paramBuffers.push(new Buffer(param));
|
||||
});
|
||||
|
||||
var totalLength = 12;
|
||||
paramBuffers.forEach(function(paramBuffer) {
|
||||
totalLength += paramBuffer.length+1+4;
|
||||
});
|
||||
|
||||
var b = new Buffer(totalLength);
|
||||
b.writeUInt32LE(0,0);
|
||||
b.writeUInt32LE(totalLength,4);
|
||||
b.writeUInt32LE(params.length,8);
|
||||
var offset = 12;
|
||||
paramBuffers.forEach(function(paramBuffer) {
|
||||
b.writeUInt32LE(paramBuffer.length, offset); offset += 4;
|
||||
paramBuffer.copy(b, offset); offset += paramBuffer.length;
|
||||
b.writeUInt8(0, offset); offset += 1;
|
||||
});
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
module.exports = require('./core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.encoding = 'latin1';
|
||||
this.options.port = 25200+22000;
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
var decoded;
|
||||
|
||||
async.series([
|
||||
function(c) {
|
||||
self.query(['serverInfo'], function(data) {
|
||||
if(self.debug) console.log(data);
|
||||
if(data.shift() != 'OK') return self.fatal('Missing OK');
|
||||
|
||||
state.raw.name = data.shift();
|
||||
state.raw.numplayers = parseInt(data.shift());
|
||||
state.maxplayers = parseInt(data.shift());
|
||||
state.raw.gametype = data.shift();
|
||||
state.map = data.shift();
|
||||
state.raw.roundsplayed = parseInt(data.shift());
|
||||
state.raw.roundstotal = parseInt(data.shift());
|
||||
|
||||
var teamCount = data.shift();
|
||||
state.raw.teams = [];
|
||||
for(var i = 0; i < teamCount; i++) {
|
||||
var tickets = parseFloat(data.shift());
|
||||
state.raw.teams.push({
|
||||
tickets:tickets
|
||||
});
|
||||
}
|
||||
|
||||
state.raw.targetscore = parseInt(data.shift());
|
||||
data.shift();
|
||||
state.raw.ranked = (data.shift() == 'true');
|
||||
state.raw.punkbuster = (data.shift() == 'true');
|
||||
state.password = (data.shift() == 'true');
|
||||
state.raw.uptime = parseInt(data.shift());
|
||||
state.raw.roundtime = parseInt(data.shift());
|
||||
if(self.isBadCompany2) {
|
||||
data.shift();
|
||||
data.shift();
|
||||
}
|
||||
state.raw.ip = data.shift();
|
||||
state.raw.punkbusterversion = data.shift();
|
||||
state.raw.joinqueue = (data.shift() == 'true');
|
||||
state.raw.region = data.shift();
|
||||
if(!self.isBadCompany2) {
|
||||
state.raw.pingsite = data.shift();
|
||||
state.raw.country = data.shift();
|
||||
state.raw.quickmatch = (data.shift() == 'true');
|
||||
}
|
||||
|
||||
c();
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
self.query(['version'], function(data) {
|
||||
if(self.debug) console.log(data);
|
||||
if(data[0] != 'OK') return self.fatal('Missing OK');
|
||||
|
||||
state.raw.version = data[2];
|
||||
|
||||
c();
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
self.query(['listPlayers','all'], function(data) {
|
||||
if(self.debug) console.log(data);
|
||||
if(data.shift() != 'OK') return self.fatal('Missing OK');
|
||||
|
||||
var fieldCount = parseInt(data.shift());
|
||||
var fields = [];
|
||||
for(var i = 0; i < fieldCount; i++) {
|
||||
fields.push(data.shift());
|
||||
}
|
||||
var numplayers = data.shift();
|
||||
for(var i = 0; i < numplayers; i++) {
|
||||
var player = {};
|
||||
fields.forEach(function(key) {
|
||||
var value = data.shift();
|
||||
|
||||
if(key == 'teamId') key = 'team';
|
||||
else if(key == 'squadId') key = 'squad';
|
||||
|
||||
if(
|
||||
key == 'kills'
|
||||
|| key == 'deaths'
|
||||
|| key == 'score'
|
||||
|| key == 'rank'
|
||||
|| key == 'team'
|
||||
|| key == 'squad'
|
||||
|| key == 'ping'
|
||||
|| key == 'type'
|
||||
) {
|
||||
value = parseInt(value);
|
||||
}
|
||||
|
||||
player[key] = value;
|
||||
});
|
||||
state.players.push(player);
|
||||
}
|
||||
|
||||
self.finish(state);
|
||||
});
|
||||
}
|
||||
]);
|
||||
},
|
||||
query: function(params,c) {
|
||||
var self = this;
|
||||
this.tcpSend(buildPacket(params), function(data) {
|
||||
var decoded = self.decodePacket(data);
|
||||
if(!decoded) return false;
|
||||
c(decoded);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
decodePacket: function(buffer) {
|
||||
if(buffer.length < 8) return false;
|
||||
var reader = this.reader(buffer);
|
||||
var header = reader.uint(4);
|
||||
var totalLength = reader.uint(4);
|
||||
if(buffer.length < totalLength) return false;
|
||||
|
||||
var paramCount = reader.uint(4);
|
||||
var params = [];
|
||||
for(var i = 0; i < paramCount; i++) {
|
||||
var len = reader.uint(4);
|
||||
params.push(reader.string({length:len}));
|
||||
var strNull = reader.uint(1);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
});
|
|
@ -165,6 +165,18 @@ module.exports = Class.extend(EventEmitter,{
|
|||
return id;
|
||||
},
|
||||
|
||||
|
||||
|
||||
trueTest: function(str) {
|
||||
if(typeof str == 'boolean') return str;
|
||||
if(typeof str == 'number') return str != 0;
|
||||
if(typeof str == 'string') {
|
||||
if(str.toLowerCase() == 'true') return true;
|
||||
if(str == 'yes') return true;
|
||||
if(str == '1') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
@ -194,10 +206,13 @@ module.exports = Class.extend(EventEmitter,{
|
|||
});
|
||||
},
|
||||
tcpSend: function(buffer,ondata) {
|
||||
if(this.tcpCallback) return this.fatal('Attempted to send TCP packet while still waiting on a managed response');
|
||||
this.tcpCallback = ondata;
|
||||
this._tcpConnect(function(socket) {
|
||||
socket.write(buffer);
|
||||
var self = this;
|
||||
process.nextTick(function() {
|
||||
if(self.tcpCallback) return self.fatal('Attempted to send TCP packet while still waiting on a managed response');
|
||||
self.tcpCallback = ondata;
|
||||
self._tcpConnect(function(socket) {
|
||||
socket.write(buffer);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -205,18 +220,19 @@ module.exports = Class.extend(EventEmitter,{
|
|||
|
||||
udpSend: function(buffer,onpacket,ontimeout) {
|
||||
var self = this;
|
||||
process.nextTick(function() {
|
||||
if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response');
|
||||
self._udpSendNow(buffer);
|
||||
if(!onpacket) return;
|
||||
|
||||
if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response');
|
||||
self._udpSendNow(buffer);
|
||||
if(!onpacket) return;
|
||||
|
||||
self.udpTimeoutTimer = self.setTimeout(function() {
|
||||
self.udpCallback = false;
|
||||
var timeout = false;
|
||||
if(!ontimeout || ontimeout() !== true) timeout = true;
|
||||
if(timeout) self.error('timeout');
|
||||
},1000);
|
||||
self.udpCallback = onpacket;
|
||||
self.udpTimeoutTimer = self.setTimeout(function() {
|
||||
self.udpCallback = false;
|
||||
var timeout = false;
|
||||
if(!ontimeout || ontimeout() !== true) timeout = true;
|
||||
if(timeout) self.error('timeout');
|
||||
},1000);
|
||||
self.udpCallback = onpacket;
|
||||
});
|
||||
},
|
||||
_udpSendNow: function(buffer) {
|
||||
if(!('port' in this.options)) return this.fatal('Attempted to send without setting a port');
|
||||
|
|
96
games/protocols/doom3.js
Normal file
96
games/protocols/doom3.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
module.exports = require('./core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Doom 3';
|
||||
this.options.port = 27666;
|
||||
this.encoding = 'latin1';
|
||||
this.isEtqw = false;
|
||||
this.hasSpaceBeforeClanTag = false;
|
||||
this.hasClanTag = false;
|
||||
this.hasTypeFlag = false;
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
|
||||
this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00',function(buffer) {
|
||||
var reader = self.reader(buffer);
|
||||
|
||||
var header = reader.uint(2);
|
||||
if(header != 0xffff) return;
|
||||
var header2 = reader.string();
|
||||
if(header2 != 'infoResponse') return;
|
||||
|
||||
var tailSize = 5;
|
||||
if(self.isEtqw) {
|
||||
var taskId = reader.uint(4);
|
||||
}
|
||||
|
||||
var challenge = reader.uint(4);
|
||||
var protoVersion = reader.uint(4);
|
||||
state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff);
|
||||
|
||||
if(self.isEtqw) {
|
||||
var size = reader.uint(4);
|
||||
}
|
||||
|
||||
while(!reader.done()) {
|
||||
var key = reader.string();
|
||||
var value = self.stripColors(reader.string());
|
||||
if(key == 'si_map') {
|
||||
value = value.replace('maps/','');
|
||||
value = value.replace('.entities','');
|
||||
}
|
||||
if(!key) break;
|
||||
state.raw[key] = value;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
while(!reader.done()) {
|
||||
i++;
|
||||
var player = {};
|
||||
player.id = reader.uint(1);
|
||||
if(player.id == 32) break;
|
||||
player.ping = reader.uint(2);
|
||||
if(!self.isEtqw) player.rate = reader.uint(4);
|
||||
player.name = self.stripColors(reader.string());
|
||||
if(self.hasClanTag) {
|
||||
if(self.hasSpaceBeforeClanTag) reader.uint(1);
|
||||
player.clantag = self.stripColors(reader.string());
|
||||
}
|
||||
if(self.hasTypeFlag) player.typeflag = reader.uint(1);
|
||||
|
||||
if(!player.ping || player.typeflag)
|
||||
state.bots.push(player);
|
||||
else
|
||||
state.players.push(player);
|
||||
}
|
||||
|
||||
state.raw.osmask = reader.uint(4);
|
||||
if(self.isEtqw) {
|
||||
state.raw.ranked = reader.uint(1);
|
||||
state.raw.timeleft = reader.uint(4);
|
||||
state.raw.gamestate = reader.uint(1);
|
||||
state.raw.servertype = reader.uint(1);
|
||||
// 0 = regular, 1 = tv
|
||||
if(state.raw.servertype == 0) {
|
||||
state.raw.interestedClients = reader.uint(1);
|
||||
} else if(state.raw.servertype == 1) {
|
||||
state.raw.connectedClients = reader.uint(4);
|
||||
state.raw.maxClients = reader.uint(4);
|
||||
}
|
||||
}
|
||||
|
||||
if(state.raw.si_name) state.name = state.raw.si_name;
|
||||
if(state.raw.si_map) state.map = state.raw.si_map;
|
||||
if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers);
|
||||
if(state.raw.si_usepass == '1') state.password = true;
|
||||
|
||||
self.finish(state);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
stripColors: function(str) {
|
||||
// uses quake 3 color codes
|
||||
return str.replace(/\^(X.{6}|.)/g,'');
|
||||
}
|
||||
});
|
|
@ -16,7 +16,7 @@ module.exports = require('./core').extend({
|
|||
state.raw = data;
|
||||
if('hostname' in state.raw) state.name = state.raw.hostname;
|
||||
if('mapname' in state.raw) state.map = state.raw.mapname;
|
||||
if(state.raw.password == '1') state.password = true;
|
||||
if(self.trueTest(state.raw.password)) state.password = true;
|
||||
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
|
||||
c();
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ module.exports = require('./core').extend({
|
|||
} else {
|
||||
if(!(id in players)) players[id] = {};
|
||||
if(key == 'playername') key = 'name';
|
||||
else if(key == 'team') value = parseInt(value)-1;
|
||||
else if(key == 'team') value = parseInt(value);
|
||||
else if(key == 'score' || key == 'ping' || key == 'deaths') value = parseInt(value);
|
||||
players[id][key] = value;
|
||||
}
|
||||
|
|
|
@ -9,31 +9,41 @@ module.exports = require('./core').extend({
|
|||
var self = this;
|
||||
|
||||
var request = new Buffer([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]);
|
||||
this.udpSend(request,function(buffer) {
|
||||
var reader = self.reader(buffer);
|
||||
var header = reader.uint(1);
|
||||
if(header != 0) return;
|
||||
var pingId = reader.uint(4);
|
||||
if(pingId != 1) return;
|
||||
|
||||
while(!reader.done()) {
|
||||
var key = reader.string();
|
||||
var value = reader.string();
|
||||
if(!key) break;
|
||||
state.raw[key] = value;
|
||||
var packets = [];
|
||||
this.udpSend(request,
|
||||
function(buffer) {
|
||||
if(packets.length && buffer.readUInt8(0) == 0)
|
||||
buffer = buffer.slice(1);
|
||||
packets.push(buffer);
|
||||
},
|
||||
function() {
|
||||
var buffer = Buffer.concat(packets);
|
||||
console.log(buffer.toString());
|
||||
var reader = self.reader(buffer);
|
||||
var header = reader.uint(1);
|
||||
if(header != 0) return;
|
||||
var pingId = reader.uint(4);
|
||||
if(pingId != 1) return;
|
||||
|
||||
while(!reader.done()) {
|
||||
var key = reader.string();
|
||||
var value = reader.string();
|
||||
if(!key) break;
|
||||
state.raw[key] = value;
|
||||
}
|
||||
|
||||
if('hostname' in state.raw) state.name = state.raw.hostname;
|
||||
if('mapname' in state.raw) state.map = state.raw.mapname;
|
||||
if(self.trueTest(state.raw.password)) state.password = true;
|
||||
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
|
||||
|
||||
state.players = self.readFieldData(reader);
|
||||
state.raw.teams = self.readFieldData(reader);
|
||||
|
||||
self.finish(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
if('hostname' in state.raw) state.name = state.raw.hostname;
|
||||
if('mapname' in state.raw) state.map = state.raw.mapname;
|
||||
if(state.raw.password == '1') state.password = true;
|
||||
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
|
||||
|
||||
state.players = self.readFieldData(reader);
|
||||
state.raw.teams = self.readFieldData(reader);
|
||||
|
||||
self.finish(state);
|
||||
return true;
|
||||
});
|
||||
);
|
||||
},
|
||||
readFieldData: function(reader) {
|
||||
var count = reader.uint(1);
|
||||
|
@ -68,6 +78,10 @@ module.exports = require('./core').extend({
|
|||
else if(key == 'kills_') key = 'kills';
|
||||
else if(key == 'team_t') key = 'name';
|
||||
else if(key == 'tickets_t') key = 'tickets';
|
||||
|
||||
if(key == 'score' || key == 'deaths' || key == 'ping' || key == 'team'
|
||||
|| key == 'kills' || key == 'tickets') value = parseInt(value);
|
||||
|
||||
unit[key] = value;
|
||||
}
|
||||
units.push(unit);
|
||||
|
|
|
@ -7,6 +7,7 @@ module.exports = require('./core').extend({
|
|||
this.encoding = 'latin1';
|
||||
this.byteorder = 'be';
|
||||
this.noChallenge = false;
|
||||
this.useOnlySingleSplit = false;
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
|
@ -17,7 +18,6 @@ module.exports = require('./core').extend({
|
|||
if(self.noChallenge) return c();
|
||||
self.sendPacket(9,false,false,false,function(buffer) {
|
||||
var reader = self.reader(buffer);
|
||||
reader.skip(5);
|
||||
challenge = parseInt(reader.string());
|
||||
c();
|
||||
});
|
||||
|
@ -125,16 +125,25 @@ module.exports = require('./core').extend({
|
|||
if(iSessionId != self.sessionId) return;
|
||||
|
||||
if(!assemble) {
|
||||
c(buffer);
|
||||
c(buffer.slice(5));
|
||||
return true;
|
||||
}
|
||||
if(self.useOnlySingleSplit) {
|
||||
// has split headers, but they are worthless and only one packet is used
|
||||
c([buffer.slice(16)]);
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = buffer.readUInt16LE(14);
|
||||
var last = (id & 0x80);
|
||||
id = id & 0x7f;
|
||||
if(last || self._singlePacketSplits) numPackets = id+1;
|
||||
if(last) numPackets = id+1;
|
||||
|
||||
packets[id] = buffer.slice(16);
|
||||
if(self.debug) {
|
||||
console.log("Received packet #"+id);
|
||||
if(last) console.log("(last)");
|
||||
}
|
||||
|
||||
if(!numPackets || Object.keys(packets).length != numPackets) return;
|
||||
|
||||
|
|
|
@ -7,17 +7,28 @@ module.exports = require('./core').extend({
|
|||
this.delimiter = '\n';
|
||||
this.sendHeader = 'status';
|
||||
this.responseHeader = 'print';
|
||||
this.isQuake1 = false;
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
|
||||
this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) {
|
||||
var reader = self.reader(buffer);
|
||||
var header = reader.string();
|
||||
if(header != '\xff\xff\xff\xff'+this.responseHeader) return;
|
||||
|
||||
var header = reader.string({length:4});
|
||||
if(header != '\xff\xff\xff\xff') return;
|
||||
|
||||
var response;
|
||||
if(this.isQuake1) {
|
||||
response = reader.string({length:1});
|
||||
} else {
|
||||
response = reader.string();
|
||||
}
|
||||
if(response != this.responseHeader) return;
|
||||
|
||||
var info = reader.string().split('\\');
|
||||
if(info[0] == '') info.shift();
|
||||
|
||||
while(true) {
|
||||
var key = info.shift();
|
||||
var value = info.shift();
|
||||
|
@ -26,10 +37,11 @@ module.exports = require('./core').extend({
|
|||
}
|
||||
|
||||
while(!reader.done()) {
|
||||
var player = reader.string();
|
||||
var line = reader.string();
|
||||
if(!line || line.charAt(0) == '\0') break;
|
||||
|
||||
var args = [];
|
||||
var split = player.split('"');
|
||||
var split = line.split('"');
|
||||
var inQuote = false;
|
||||
split.forEach(function(part,i) {
|
||||
var inQuote = (i%2 == 1);
|
||||
|
@ -43,14 +55,24 @@ module.exports = require('./core').extend({
|
|||
}
|
||||
});
|
||||
|
||||
var frags = parseInt(args[0]);
|
||||
var ping = parseInt(args[1]);
|
||||
var name = args[2] || '';
|
||||
var address = args[3] || '';
|
||||
var player = {};
|
||||
if(self.isQuake1) {
|
||||
player.id = parseInt(args.shift());
|
||||
player.score = parseInt(args.shift());
|
||||
player.time = parseInt(args.shift());
|
||||
player.ping = parseInt(args.shift());
|
||||
player.name = args.shift();
|
||||
player.skin = args.shift();
|
||||
player.color1 = parseInt(args.shift());
|
||||
player.color2 = parseInt(args.shift());
|
||||
} else {
|
||||
player.frags = parseInt(args.shift());
|
||||
player.ping = parseInt(args.shift());
|
||||
player.name = args.shift() || '';
|
||||
player.address = args.shift() || '';
|
||||
}
|
||||
|
||||
(ping == 0 ? state.bots : state.players).push({
|
||||
frags:frags, ping:ping, name:name, address:address
|
||||
});
|
||||
(player.ping ? state.players : state.bots).push(player);
|
||||
}
|
||||
|
||||
if('g_needpass' in state.raw) state.password = state.raw.g_needpass;
|
||||
|
|
|
@ -5,168 +5,189 @@ module.exports = require('./core').extend({
|
|||
init: function() {
|
||||
this._super();
|
||||
this.goldsrc = false;
|
||||
this.legacyChallenge = false;
|
||||
this.options.port = 27015;
|
||||
|
||||
// 2006 engines don't pass packet switching size in split packet header
|
||||
// while all others do
|
||||
this._skipSizeInSplitHeader = false;
|
||||
|
||||
this._challenge = '';
|
||||
},
|
||||
run: function(state) {
|
||||
|
||||
var self = this;
|
||||
var challenge;
|
||||
|
||||
async.series([
|
||||
function(c) {
|
||||
self.sendPacket(
|
||||
0x54,false,new Buffer('Source Engine Query\0'),
|
||||
self.goldsrc ? 0x6D : 0x49,
|
||||
function(b) {
|
||||
var reader = self.reader(b);
|
||||
|
||||
if(self.goldsrc) state.raw.address = reader.string();
|
||||
else state.raw.protocol = reader.uint(1);
|
||||
|
||||
state.name = reader.string();
|
||||
state.map = reader.string();
|
||||
state.raw.folder = reader.string();
|
||||
state.raw.game = reader.string();
|
||||
state.raw.steamappid = reader.uint(2);
|
||||
state.raw.numplayers = reader.uint(1);
|
||||
state.maxplayers = reader.uint(1);
|
||||
|
||||
if(self.goldsrc) state.raw.protocol = reader.uint(1);
|
||||
else state.raw.numbots = 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) {
|
||||
state.raw.modlink = reader.string();
|
||||
state.raw.moddownload = reader.string();
|
||||
reader.skip(1);
|
||||
state.raw.modversion = reader.uint(4);
|
||||
state.raw.modsize = reader.uint(4);
|
||||
state.raw.modtype = reader.uint(1);
|
||||
state.raw.moddll = reader.uint(1);
|
||||
}
|
||||
}
|
||||
state.raw.secure = reader.uint(1);
|
||||
|
||||
if(self.goldsrc) {
|
||||
state.raw.numbots = reader.uint(1);
|
||||
} else {
|
||||
if(state.raw.folder == 'ship') {
|
||||
state.raw.shipmode = reader.uint(1);
|
||||
state.raw.shipwitnesses = reader.uint(1);
|
||||
state.raw.shipduration = reader.uint(1);
|
||||
}
|
||||
state.raw.version = reader.string();
|
||||
var extraFlag = reader.uint(1);
|
||||
if(extraFlag & 0x80) state.raw.port = reader.uint(2);
|
||||
if(extraFlag & 0x10) state.raw.steamid = reader.uint(8);
|
||||
if(extraFlag & 0x40) {
|
||||
state.raw.sourcetvport = reader.uint(2);
|
||||
state.raw.sourcetvname = reader.string();
|
||||
}
|
||||
if(extraFlag & 0x20) state.raw.tags = reader.string();
|
||||
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(self.goldsrc?0x56:0x55,0xffffffff,false,0x41,function(b) {
|
||||
var reader = self.reader(b);
|
||||
challenge = reader.uint(4);
|
||||
c();
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
self.sendPacket(0x55,challenge,false,0x44,function(b) {
|
||||
var reader = self.reader(b);
|
||||
var num = reader.uint(1);
|
||||
for(var i = 0; i < num; i++) {
|
||||
reader.skip(1);
|
||||
var name = reader.string();
|
||||
var score = reader.int(4);
|
||||
var time = reader.float();
|
||||
|
||||
// connecting players don't could as players.
|
||||
if(!name) continue;
|
||||
|
||||
(time == -1 ? state.bots : state.players).push({
|
||||
name:name, score:score, time:time
|
||||
});
|
||||
}
|
||||
|
||||
// if we didn't find the bots, iterate
|
||||
// through and guess which ones they are
|
||||
if(!state.bots.length) {
|
||||
var maxTime = 0;
|
||||
state.players.forEach(function(player) {
|
||||
maxTime = Math.max(player.time,maxTime);
|
||||
});
|
||||
for(var i = 0; i < state.players.length; i++) {
|
||||
var player = state.players[i];
|
||||
if(state.bots.length >= state.raw.numbots) continue;
|
||||
if(player.time != maxTime) continue;
|
||||
state.bots.push(player);
|
||||
state.players.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
c();
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
self.sendPacket(0x56,challenge,false,0x45,function(b) {
|
||||
var reader = self.reader(b);
|
||||
var num = reader.uint(2);
|
||||
state.raw.rules = [];
|
||||
for(var i = 0; i < num; i++) {
|
||||
var key = reader.string();
|
||||
var value = reader.string();
|
||||
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) {
|
||||
self.finish(state);
|
||||
}
|
||||
function(c) { self.queryInfo(state,c); },
|
||||
function(c) { self.queryChallenge(state,c); },
|
||||
function(c) { self.queryPlayers(state,c); },
|
||||
function(c) { self.queryRules(state,c); },
|
||||
function(c) { self.finish(state); }
|
||||
]);
|
||||
},
|
||||
sendPacket: function(type,challenge,payload,expect,callback,ontimeout) {
|
||||
queryInfo: function(state,c) {
|
||||
var self = this;
|
||||
self.sendPacket(
|
||||
0x54,false,'Source Engine Query\0',
|
||||
self.goldsrc ? 0x6D : 0x49,
|
||||
function(b) {
|
||||
var reader = self.reader(b);
|
||||
|
||||
if(self.goldsrc) state.raw.address = reader.string();
|
||||
else state.raw.protocol = reader.uint(1);
|
||||
|
||||
state.name = reader.string();
|
||||
state.map = reader.string();
|
||||
state.raw.folder = reader.string();
|
||||
state.raw.game = reader.string();
|
||||
state.raw.steamappid = reader.uint(2);
|
||||
state.raw.numplayers = reader.uint(1);
|
||||
state.maxplayers = reader.uint(1);
|
||||
|
||||
if(self.goldsrc) state.raw.protocol = reader.uint(1);
|
||||
else state.raw.numbots = 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) {
|
||||
state.raw.modlink = reader.string();
|
||||
state.raw.moddownload = reader.string();
|
||||
reader.skip(1);
|
||||
state.raw.modversion = reader.uint(4);
|
||||
state.raw.modsize = reader.uint(4);
|
||||
state.raw.modtype = reader.uint(1);
|
||||
state.raw.moddll = reader.uint(1);
|
||||
}
|
||||
}
|
||||
state.raw.secure = reader.uint(1);
|
||||
|
||||
if(self.goldsrc) {
|
||||
state.raw.numbots = reader.uint(1);
|
||||
} else {
|
||||
if(state.raw.folder == 'ship') {
|
||||
state.raw.shipmode = reader.uint(1);
|
||||
state.raw.shipwitnesses = reader.uint(1);
|
||||
state.raw.shipduration = reader.uint(1);
|
||||
}
|
||||
state.raw.version = reader.string();
|
||||
var extraFlag = reader.uint(1);
|
||||
if(extraFlag & 0x80) state.raw.port = reader.uint(2);
|
||||
if(extraFlag & 0x10) state.raw.steamid = reader.uint(8);
|
||||
if(extraFlag & 0x40) {
|
||||
state.raw.sourcetvport = reader.uint(2);
|
||||
state.raw.sourcetvname = reader.string();
|
||||
}
|
||||
if(extraFlag & 0x20) state.raw.tags = reader.string();
|
||||
if(extraFlag & 0x01) state.raw.gameid = reader.uint(8);
|
||||
}
|
||||
|
||||
if(state.raw.protocol == 7 && state.raw.steamappid == 215) {
|
||||
self._skipSizeInSplitHeader = true;
|
||||
}
|
||||
|
||||
c();
|
||||
}
|
||||
);
|
||||
},
|
||||
queryChallenge: function(state,c) {
|
||||
var self = this;
|
||||
if(this.legacyChallenge) {
|
||||
self.sendPacket(0x57,false,false,0x41,function(b) {
|
||||
var reader = self.reader(b);
|
||||
self._challenge = reader.uint(4);
|
||||
c();
|
||||
});
|
||||
} else {
|
||||
self.sendPacket(self.goldsrc?0x56:0x55,0xffffffff,false,0x41,function(b) {
|
||||
var reader = self.reader(b);
|
||||
self._challenge = reader.uint(4);
|
||||
c();
|
||||
});
|
||||
}
|
||||
},
|
||||
queryPlayers: function(state,c) {
|
||||
var self = this;
|
||||
self.sendPacket(0x55,true,false,0x44,function(b) {
|
||||
var reader = self.reader(b);
|
||||
var num = reader.uint(1);
|
||||
for(var i = 0; i < num; i++) {
|
||||
reader.skip(1);
|
||||
var name = reader.string();
|
||||
var score = reader.int(4);
|
||||
var time = reader.float();
|
||||
|
||||
// connecting players don't could as players.
|
||||
if(!name) continue;
|
||||
|
||||
(time == -1 ? state.bots : state.players).push({
|
||||
name:name, score:score, time:time
|
||||
});
|
||||
}
|
||||
|
||||
// if we didn't find the bots, iterate
|
||||
// through and guess which ones they are
|
||||
if(!state.bots.length && state.raw.numbots) {
|
||||
var maxTime = 0;
|
||||
state.players.forEach(function(player) {
|
||||
maxTime = Math.max(player.time,maxTime);
|
||||
});
|
||||
for(var i = 0; i < state.players.length; i++) {
|
||||
var player = state.players[i];
|
||||
if(state.bots.length >= state.raw.numbots) continue;
|
||||
if(player.time != maxTime) continue;
|
||||
state.bots.push(player);
|
||||
state.players.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
c();
|
||||
});
|
||||
},
|
||||
queryRules: function(state,c) {
|
||||
var self = this;
|
||||
self.sendPacket(0x56,true,false,0x45,function(b) {
|
||||
var reader = self.reader(b);
|
||||
var num = reader.uint(2);
|
||||
state.raw.rules = {};
|
||||
for(var i = 0; i < num; i++) {
|
||||
var key = reader.string();
|
||||
var value = reader.string();
|
||||
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;
|
||||
});
|
||||
},
|
||||
sendPacket: function(type,sendChallenge,payload,expect,callback,ontimeout) {
|
||||
var self = this;
|
||||
|
||||
var challengeLength = challenge === false ? 0 : 4;
|
||||
if(typeof payload == 'string') payload = new Buffer(payload);
|
||||
var challengeLength = sendChallenge !== false ? 4 : 0;
|
||||
var payloadLength = payload ? payload.length : 0;
|
||||
|
||||
var b = new Buffer(5 + challengeLength + payloadLength);
|
||||
b.writeInt32LE(-1, 0);
|
||||
b.writeUInt8(type, 4);
|
||||
if(challengeLength) b.writeUInt32LE(challenge, 5);
|
||||
|
||||
if(sendChallenge !== false) {
|
||||
var challenge = this._challenge;
|
||||
if(typeof sendChallenge == 'number') challenge = sendChallenge;
|
||||
if(self.byteorder == 'le') b.writeUInt32LE(challenge, 5);
|
||||
else b.writeUInt32BE(challenge, 5);
|
||||
}
|
||||
if(payloadLength) payload.copy(b, 5+challengeLength);
|
||||
|
||||
function received(payload) {
|
||||
|
@ -210,6 +231,7 @@ module.exports = require('./core').extend({
|
|||
if(self.debug) {
|
||||
console.log("Received partial packet id: "+id);
|
||||
console.log("Expecting "+numPackets+" packets, have "+Object.keys(packets).length);
|
||||
console.log("Bzip? "+bzip);
|
||||
}
|
||||
|
||||
if(!numPackets || Object.keys(packets).length != numPackets) return;
|
||||
|
|
9
games/quake1.js
Normal file
9
games/quake1.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = require('./protocols/quake2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Quake 1';
|
||||
this.options.port = 27500;
|
||||
this.responseHeader = 'n';
|
||||
this.isQuake1 = true;
|
||||
}
|
||||
});
|
8
games/quake4.js
Normal file
8
games/quake4.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = require('./protocols/doom3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Quake 4';
|
||||
this.hasClanTag = true;
|
||||
this.options.port = 28004;
|
||||
}
|
||||
});
|
12
games/wolfenstein2009.js
Normal file
12
games/wolfenstein2009.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// this was assembled from old docs and not tested
|
||||
// hopefully it still works
|
||||
|
||||
module.exports = require('./protocols/doom3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Wolfenstein 2009';
|
||||
this.hasSpaceBeforeClanTag = true;
|
||||
this.hasClanTag = true;
|
||||
this.hasTypeFlag = true;
|
||||
}
|
||||
});
|
|
@ -42,14 +42,19 @@ Reader.prototype = {
|
|||
var delim = options.delimiter || this.query.delimiter;
|
||||
if(typeof delim == 'string') delim = delim.charCodeAt(0);
|
||||
while(true) {
|
||||
if(end >= this.buffer.length) return '';
|
||||
if(end >= this.buffer.length) {
|
||||
end = this.buffer.length;
|
||||
break;
|
||||
}
|
||||
if(this.buffer.readUInt8(end) == delim) break;
|
||||
end++;
|
||||
}
|
||||
this.i = end+1;
|
||||
} else {
|
||||
end = start+options.length;
|
||||
if(end > this.buffer.length) return '';
|
||||
if(end >= this.buffer.length) {
|
||||
end = this.buffer.length;
|
||||
}
|
||||
this.i = end;
|
||||
}
|
||||
|
||||
|
@ -64,7 +69,7 @@ Reader.prototype = {
|
|||
},
|
||||
int: function(bytes) {
|
||||
var r = 0;
|
||||
if(this.i+bytes <= this.buffer.length) {
|
||||
if(this.remaining() >= bytes) {
|
||||
if(this.query.byteorder == 'be') {
|
||||
if(bytes == 1) r = this.buffer.readInt8(this.i);
|
||||
else if(bytes == 2) r = this.buffer.readInt16BE(this.i);
|
||||
|
@ -80,7 +85,7 @@ Reader.prototype = {
|
|||
},
|
||||
uint: function(bytes) {
|
||||
var r = 0;
|
||||
if(this.i+bytes <= this.buffer.length) {
|
||||
if(this.remaining() >= bytes) {
|
||||
if(this.query.byteorder == 'be') {
|
||||
if(bytes == 1) r = this.buffer.readUInt8(this.i);
|
||||
else if(bytes == 2) r = this.buffer.readUInt16BE(this.i);
|
||||
|
@ -98,13 +103,16 @@ Reader.prototype = {
|
|||
},
|
||||
float: function() {
|
||||
var r = 0;
|
||||
if(this.i+4 <= this.buffer.length) {
|
||||
if(this.remaining() >= 4) {
|
||||
if(this.query.byteorder == 'be') r = this.buffer.readFloatBE(this.i);
|
||||
else r = this.buffer.readFloatLE(this.i);
|
||||
}
|
||||
this.i += 4;
|
||||
return r;
|
||||
},
|
||||
remaining: function() {
|
||||
return this.buffer.length-this.i;
|
||||
},
|
||||
rest: function() {
|
||||
return this.buffer.slice(this.i);
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue