2017-08-09 11:05:55 +02:00
|
|
|
const async = require('async');
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 11:05:55 +02:00
|
|
|
class Unreal2 extends require('./core') {
|
2017-08-09 12:32:09 +02:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.encoding = 'latin1';
|
|
|
|
}
|
|
|
|
run(state) {
|
|
|
|
async.series([
|
|
|
|
(c) => {
|
|
|
|
this.sendPacket(0,true,(b) => {
|
|
|
|
const reader = this.reader(b);
|
|
|
|
state.raw.serverid = reader.uint(4);
|
|
|
|
state.raw.ip = this.readUnrealString(reader);
|
|
|
|
state.raw.port = reader.uint(4);
|
|
|
|
state.raw.queryport = reader.uint(4);
|
|
|
|
state.name = this.readUnrealString(reader,true);
|
|
|
|
state.map = this.readUnrealString(reader,true);
|
|
|
|
state.raw.gametype = this.readUnrealString(reader,true);
|
|
|
|
state.raw.numplayers = reader.uint(4);
|
|
|
|
state.maxplayers = reader.uint(4);
|
|
|
|
this.readExtraInfo(reader,state);
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
c();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
(c) => {
|
|
|
|
this.sendPacket(1,true,(b) => {
|
2017-08-09 11:05:55 +02:00
|
|
|
const reader = this.reader(b);
|
2017-08-09 12:32:09 +02:00
|
|
|
state.raw.mutators = [];
|
|
|
|
state.raw.rules = {};
|
|
|
|
while(!reader.done()) {
|
2017-08-09 11:05:55 +02:00
|
|
|
const key = this.readUnrealString(reader,true);
|
|
|
|
const value = this.readUnrealString(reader,true);
|
2017-08-09 12:32:09 +02:00
|
|
|
if(key === 'Mutator') state.raw.mutators.push(value);
|
|
|
|
else state.raw.rules[key] = value;
|
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
if('GamePassword' in state.raw.rules)
|
|
|
|
state.password = state.raw.rules.GamePassword !== 'True';
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
c();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
(c) => {
|
|
|
|
this.sendPacket(2,false,(b) => {
|
2017-08-09 11:05:55 +02:00
|
|
|
const reader = this.reader(b);
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
while(!reader.done()) {
|
2017-08-09 11:05:55 +02:00
|
|
|
const player = {};
|
2017-08-09 12:32:09 +02:00
|
|
|
player.id = reader.uint(4);
|
|
|
|
if(!player.id) break;
|
|
|
|
if(player.id === 0) {
|
|
|
|
// Unreal2XMP Player (ID is always 0)
|
|
|
|
reader.skip(4);
|
|
|
|
}
|
|
|
|
player.name = this.readUnrealString(reader,true);
|
|
|
|
player.ping = reader.uint(4);
|
|
|
|
player.score = reader.int(4);
|
|
|
|
reader.skip(4); // stats ID
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
// Extra data for Unreal2XMP players
|
|
|
|
if(player.id === 0) {
|
2017-08-09 11:05:55 +02:00
|
|
|
const count = reader.uint(1);
|
2017-08-09 12:32:09 +02:00
|
|
|
for(let iField = 0; iField < count; iField++) {
|
2017-08-09 11:05:55 +02:00
|
|
|
const key = this.readUnrealString(reader,true);
|
|
|
|
const value = this.readUnrealString(reader,true);
|
2017-08-09 12:32:09 +02:00
|
|
|
player[key] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(player.id === 0 && player.name === 'Player') {
|
|
|
|
// these show up in ut2004 queries, but aren't real
|
|
|
|
// not even really sure why they're there
|
|
|
|
continue;
|
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
(player.ping ? state.players : state.bots).push(player);
|
|
|
|
}
|
|
|
|
c();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
(c) => {
|
|
|
|
this.finish(state);
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
readExtraInfo(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(reader, stripColor) {
|
2017-08-09 11:05:55 +02:00
|
|
|
let length = reader.uint(1);
|
|
|
|
let out;
|
2017-08-09 12:32:09 +02:00
|
|
|
if(length < 0x80) {
|
|
|
|
//out = reader.string({length:length});
|
|
|
|
out = '';
|
|
|
|
if(length > 0) out = reader.string();
|
|
|
|
} else {
|
|
|
|
length = (length&0x7f)*2;
|
|
|
|
if(this.debug) {
|
|
|
|
console.log("UCS2 STRING");
|
|
|
|
console.log(length,reader.buffer.slice(reader.i,reader.i+length));
|
|
|
|
}
|
|
|
|
out = 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,'');
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
return out;
|
|
|
|
}
|
|
|
|
sendPacket(type,required,callback) {
|
2017-08-09 11:05:55 +02:00
|
|
|
const outbuffer = Buffer.from([0x79,0,0,0,type]);
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 11:05:55 +02:00
|
|
|
const packets = [];
|
2017-08-09 12:32:09 +02:00
|
|
|
this.udpSend(outbuffer,(buffer) => {
|
2017-08-09 11:05:55 +02:00
|
|
|
const reader = this.reader(buffer);
|
|
|
|
const header = reader.uint(4);
|
|
|
|
const iType = reader.uint(1);
|
2017-08-09 12:32:09 +02:00
|
|
|
if(iType !== type) return;
|
|
|
|
packets.push(reader.rest());
|
|
|
|
}, () => {
|
|
|
|
if(!packets.length && required) return;
|
|
|
|
callback(Buffer.concat(packets));
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
2017-08-09 11:05:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Unreal2;
|