Merge pull request #80 from roblabla/feature-opportunisticParsing

Add opportunistic parsing and raw read/write.
This commit is contained in:
Robin Lambertz 2014-04-11 19:37:49 +02:00
commit 05d1b8e1ed
4 changed files with 72 additions and 13 deletions

View File

@ -177,14 +177,14 @@ function createServer(options) {
client.end("Failed to verify username!");
return;
}
client.UUID = uuid;
client.uuid = uuid;
loginClient();
});
}
}
function loginClient() {
client.write(0x02, {uuid: (client.UUID | 0).toString(10), username: client.username});
client.write(0x02, {uuid: (client.uuid | 0).toString(10), username: client.username});
client.state = states.PLAY;
loggedIn = true;
startKeepAlive();

View File

@ -30,9 +30,48 @@ function Client(isServer) {
this.encryptionEnabled = false;
this.cipher = null;
this.decipher = null;
this.packetsToParse = {};
this.on('newListener', function(event, listener) {
var direction = this.isServer ? 'toServer' : 'toClient';
if (protocol.packetStates[direction].hasOwnProperty(event) || event === "packet") {
if (typeof this.packetsToParse[event] === "undefined") this.packetsToParse[event] = 1;
else this.packetsToParse[event] += 1;
}
});
this.on('removeListener', function(event, listener) {
var direction = this.isServer ? 'toServer' : 'toClient';
if (protocol.packetStates[direction].hasOwnProperty(event) || event === "packet") {
this.packetsToParse[event] -= 1;
}
});
}
util.inherits(Client, EventEmitter);
// Transform weird "packet" types into string representing their type. Should be mostly retro-compatible
Client.prototype.on = function(type, func) {
var direction = this.isServer ? 'toServer' : 'toClient';
if (Array.isArray(type)) {
arguments[0] = protocol.packetNames[type[0]][direction][type[1]];
} else if (typeof type === "number") {
arguments[0] = protocol.packetNames[this.state][direction][type];
}
EventEmitter.prototype.on.apply(this, arguments);
};
Client.prototype.onRaw = function(type, func) {
var arg = "raw.";
if (Array.isArray(type)) {
arg += protocol.packetNames[type[0]][direction][type[1]];
} else if (typeof type === "number") {
arg += protocol.packetNames[this.state][direction][type];
} else {
arg += type;
}
arguments[0] = arg;
EventEmitter.prototype.on.apply(this, arguments);
};
Client.prototype.setSocket = function(socket) {
var self = this;
self.socket = socket;
@ -42,7 +81,7 @@ Client.prototype.setSocket = function(socket) {
incomingBuffer = Buffer.concat([incomingBuffer, data]);
var parsed, packet;
while (true) {
parsed = parsePacket(incomingBuffer, self.state, self.isServer);
parsed = parsePacket(incomingBuffer, self.state, self.isServer, self.packetsToParse);
if (! parsed) break;
if (parsed.error) {
this.emit('error', parsed.error);
@ -53,11 +92,9 @@ Client.prototype.setSocket = function(socket) {
incomingBuffer = incomingBuffer.slice(parsed.size);
var packetName = protocol.packetNames[self.state][self.isServer ? 'toServer' : 'toClient'][packet.id];
if (packetName !== undefined) self.emit(packetName, packet);
self.emit([self.state, packet.id], packet);
self.emit(packet.id, packet);
self.emit(packetName, packet);
self.emit('packet', packet);
self.emit('raw.' + packetName, parsed.buffer);
}
});
@ -120,3 +157,11 @@ Client.prototype.write = function(packetId, params) {
this.socket.write(out);
return true;
};
Client.prototype.writeRaw = function(buffer, shouldEncrypt) {
if (shouldEncrypt === null) {
shouldEncrypt = true;
}
var out = (shouldEncrypt && this.encryptionEnabled) ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
this.socket.write(out);
};

View File

@ -1270,7 +1270,7 @@ function createPacketBuffer(packetId, state, params, isServer) {
return buffer;
}
function parsePacket(buffer, state, isServer) {
function parsePacket(buffer, state, isServer, packetsToParse) {
if (state == null) state == states.PLAY;
var cursor = 0;
var lengthField = readVarInt(buffer, 0);
@ -1279,19 +1279,32 @@ function parsePacket(buffer, state, isServer) {
cursor += lengthField.size;
if (length + lengthField.size > buffer.length) return null;
var buffer = buffer.slice(0, length + cursor); // fail early if too much is read.
var packetIdField = readVarInt(buffer, lengthField.size);
var packetIdField = readVarInt(buffer, cursor);
var packetId = packetIdField.value;
cursor += packetIdField.size;
var results = { id: packetId };
// Only parse the packet if there is a need for it, AKA if there is a listener attached to it
var name = packetNames[state][isServer ? "toServer" : "toClient"][packetId];
var shouldParse = (!packetsToParse.hasOwnProperty(name) || packetsToParse[name] <= 0)
&& (!packetsToParse.hasOwnProperty("packet") || packetsToParse["packet"] <= 0);
if (shouldParse) {
return {
size: length + lengthField.size,
buffer: buffer,
results: results
};
}
var packetInfo = get(packetId, state, isServer);
if (packetInfo === null) {
return {
error: new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")"),
size: length + lengthField.size,
buffer: buffer,
results: results
}
};
} else {
debug("read packetId " + packetId + " (0x" + packetId.toString(16) + ")");
}
@ -1323,6 +1336,7 @@ function parsePacket(buffer, state, isServer) {
return {
size: length + lengthField.size,
results: results,
buffer: buffer
};
}
@ -1336,6 +1350,7 @@ module.exports = {
packetIds: packetIds,
packetNames: packetNames,
packetFields: packetFields,
packetStates: packetStates,
states: states,
get: get,
debug: debug,

View File

@ -80,7 +80,6 @@ function joinServer(username, serverId, accessToken, selectedProfile, cb) {
function validateSession(username, serverId, cb) {
superagent.get("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + username + "&serverId=" + serverId)
.end(function(resp) {
console.log(resp.body);
if (resp.ok) {
if ("id" in resp.body) {
cb(null, resp.body.id);