Fix compression. Implement position. Add some debugging utilities.

This commit is contained in:
roblabla 2015-01-01 22:20:47 +00:00
parent 00241c4044
commit d657371b6b
4 changed files with 106 additions and 48 deletions

View File

@ -1,7 +1,7 @@
var mc = require('../');
var options = {
// 'online-mode': false, // optional
'online-mode': true,
};
var server = mc.createServer(options);
@ -21,15 +21,17 @@ server.on('login', function(client) {
gameMode: 0,
dimension: 0,
difficulty: 2,
maxPlayers: server.maxPlayers
maxPlayers: server.maxPlayers,
reducedDebugInfo: false
});
client.write('position', {
x: 0,
y: 1.62,
z: 0,
yaw: 0,
pitch: 0,
onGround: true
flags: 0x00
});
var msg = {
@ -39,7 +41,7 @@ server.on('login', function(client) {
'Hello, world!'
]
};
client.write('chat', { message: JSON.stringify(msg) });
client.write('chat', { message: JSON.stringify(msg), position: 0 });
});
server.on('error', function(error) {

View File

@ -44,7 +44,6 @@ function createServer(options) {
var kickTimeout = options.kickTimeout || 10 * 1000;
var checkTimeoutInterval = options.checkTimeoutInterval || 4 * 1000;
var onlineMode = options['online-mode'] == null ? true : options['online-mode'];
var encryptionEnabled = options.encryption == null ? true : options.encryption;
var serverKey = ursa.generatePrivateKey(1024);
@ -128,8 +127,8 @@ function createServer(options) {
function onLogin(packet) {
client.username = packet.username;
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException);
if (encryptionEnabled || needToVerify) {
var needToVerify = (onlineMode && !isException) || (! onlineMode && isException);
if (needToVerify) {
var serverId = crypto.randomBytes(4).toString('hex');
client.verifyToken = crypto.randomBytes(4);
var publicKeyStrArr = serverKey.toPublicPem("utf8").split("\n");
@ -141,8 +140,6 @@ function createServer(options) {
hash = crypto.createHash("sha1");
hash.update(serverId);
client.once([states.LOGIN, 0x01], onEncryptionKeyResponse);
client.once([states.LOGIN, 0x03], onCompressionRequest);
client.on( [states.PLAY, 0x46], onCompressionRequest);
client.write(0x01, {
serverId: serverId,
publicKey: client.publicKey,
@ -195,15 +192,13 @@ function createServer(options) {
}
}
function onCompressionRequest(packet) {
client.compressionThreshold = packet.threshold;
}
function loginClient() {
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
if (onlineMode == false || isException) {
client.uuid = "0-0-0-0-0";
}
//client.write('compress', { threshold: 256 }); // Default threshold is 256
//client.compressionThreshold = 256;
client.write(0x02, {uuid: client.uuid, username: client.username});
client.state = states.PLAY;
loggedIn = true;
@ -240,7 +235,8 @@ function createClient(options) {
if (keepAlive) client.on([states.PLAY, 0x00], onKeepAlive);
client.once([states.LOGIN, 0x01], onEncryptionKeyRequest);
client.once([states.LOGIN, 0x02], onLogin);
client.once("compress", onCompressionRequest);
client.once("set_compression", onCompressionRequest);
if (haveCredentials) {
// make a request to get the case-correct username before connecting.
var cb = function(err, session) {
@ -254,7 +250,7 @@ function createClient(options) {
client.connect(port, host);
}
};
if (accessToken != null) getSession(options.username, accessToken, options.clientToken, true, cb);
else getSession(options.username, options.password, options.clientToken, false, cb);
} else {
@ -279,6 +275,10 @@ function createClient(options) {
});
}
function onCompressionRequest(packet) {
client.compressionThreshold = packet.threshold;
}
function onKeepAlive(packet) {
client.write(0x00, {
keepAliveId: packet.keepAliveId

View File

@ -5,7 +5,11 @@ var net = require('net')
, dns = require('dns')
, createPacketBuffer = protocol.createPacketBuffer
, compressPacketBuffer = protocol.compressPacketBuffer
, oldStylePacket = protocol.oldStylePacket
, newStylePacket = protocol.newStylePacket
, parsePacket = protocol.parsePacket
, packetIds = protocol.packetIds
, packetNames = protocol.packetNames
, states = protocol.states
, debug = protocol.debug
;
@ -31,7 +35,7 @@ function Client(isServer) {
this.encryptionEnabled = false;
this.cipher = null;
this.decipher = null;
this.compressionThreshold = -1;
this.compressionThreshold = -2;
this.packetsToParse = {};
this.on('newListener', function(event, listener) {
var direction = this.isServer ? 'toServer' : 'toClient';
@ -154,28 +158,36 @@ Client.prototype.write = function(packetId, params) {
return false;
packetId = packetId[1];
}
if (typeof packetId === "string")
packetId = packetIds[this.state][this.isServer ? "toClient" : "toServer"][packetId];
var that = this;
// TODO: Which comes first, encryption or compression?
var finishWriting = function(buffer) {
debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")");
debug(params);
var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
that.socket.write(out);
return true;
var packetName = packetNames[that.state][that.isServer ? "toClient" : "toServer"][packetId];
debug("writing packetId " + that.state + "." + packetName + " (0x" + packetId.toString(16) + ")");
debug(params);
debug(buffer);
var out = that.encryptionEnabled ? new Buffer(that.cipher.update(buffer), 'binary') : buffer;
that.socket.write(out);
return true;
}
var buffer = createPacketBuffer(packetId, this.state, params, this.isServer);
if(this.compressionThreshold != -1 && buffer.length > this.compressionThreshold) {
compressPacketBuffer(buffer, finishWriting);
if (this.compressionThreshold >= 0 && buffer.length >= this.compressionThreshold) {
debug("Compressing packet");
compressPacketBuffer(buffer, finishWriting);
} else if (this.compressionThreshold >= -1) {
debug("New-styling packet");
finishWriting(newStylePacket(buffer));
} else {
finishWriting(buffer);
debug("Old-styling packet");
finishWriting(oldStylePacket(buffer));
}
};
// TODO : Perhaps this should only accept buffers without length, so we can
// handle compression ourself ? Needs to ask peopl who actually use this feature
// like @deathcap
Client.prototype.writeRaw = function(buffer, shouldEncrypt) {
if (shouldEncrypt === null) {
shouldEncrypt = true;

View File

@ -51,9 +51,9 @@ var packets = {
]},
encryption_begin: {id: 0x01, fields: [
{ name: "serverId", type: "string" },
{ name: "publicKeyLength", type: "count", typeArgs: { type: "short", countFor: "publicKey" } },
{ name: "publicKeyLength", type: "count", typeArgs: { type: "varint", countFor: "publicKey" } },
{ name: "publicKey", type: "buffer", typeArgs: { count: "publicKeyLength" } },
{ name: "verifyTokenLength", type: "count", typeArgs: { type: "short", countFor: "verifyToken" } },
{ name: "verifyTokenLength", type: "count", typeArgs: { type: "varint", countFor: "verifyToken" } },
{ name: "verifyToken", type: "buffer", typeArgs: { count: "verifyTokenLength" } },
]},
success: {id: 0x02, fields: [
@ -69,9 +69,9 @@ var packets = {
{ name: "username", type: "string" }
]},
encryption_begin: {id: 0x01, fields: [
{ name: "sharedSecretLength", type: "count", typeArgs: { type: "short", countFor: "sharedSecret" } },
{ name: "sharedSecretLength", type: "count", typeArgs: { type: "varint", countFor: "sharedSecret" } },
{ name: "sharedSecret", type: "buffer", typeArgs: { count: "sharedSecretLength" } },
{ name: "verifyTokenLength", type: "count", typeArgs: { type: "short", countFor: "verifyToken" } },
{ name: "verifyTokenLength", type: "count", typeArgs: { type: "varint", countFor: "verifyToken" } },
{ name: "verifyToken", type: "buffer", typeArgs: { count: "verifyTokenLength" } },
]}
}
@ -131,7 +131,7 @@ var packets = {
]},
bed: {id: 0x0a, fields: [
{ name: "entityId", type: "int" },
{ name: "position", type: "location" }
{ name: "location", type: "position" }
]},
animation: {id: 0x0b, fields: [
{ name: "entityId", type: "varint" },
@ -841,6 +841,7 @@ var types = {
'restBuffer': [readRestBuffer, writeRestBuffer, sizeOfRestBuffer],
'count': [readCount, writeCount, sizeOfCount],
// TODO : remove type-specific, replace with generic containers and arrays.
'position': [readPosition, writePosition, 8],
'slot': [readSlot, writeSlot, sizeOfSlot],
'entityMetadata': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
};
@ -1055,6 +1056,17 @@ function readBool(buffer, offset) {
};
}
function readPosition(buffer, offset) {
var longVal = readLong(buffer, offset).value; // I wish I could do destructuring...
var x = longVal[0] >> 6;
var y = ((longVal[0] & 0x3F) << 6) | (longVal[1] >> 26);
var z = longVal[1] << 6 >> 6
return {
value: { x: x, y: y, z: z },
size: 8
};
}
function readSlot(buffer, offset) {
var results = readShort(buffer, offset);
if (! results) return null;
@ -1093,6 +1105,13 @@ function sizeOfSlot(value) {
return value.id === -1 ? 2 : 7 + value.nbtData.length;
}
function writePosition(value, buffer, offset) {
var longVal = [];
longVal[0] = ((value.x & 0x3FFFFFF) << 6) | ((value.y & 0xFC0) >> 6);
longVal[1] = ((value.y & 0x3F) << 26) | (value.z & 0x3FFFFFF);
return writeLong(longVal, buffer, offset);
}
function writeSlot(value, buffer, offset) {
buffer.writeInt16BE(value.id, offset);
if (value.id === -1) return offset + 2;
@ -1233,6 +1252,8 @@ function readContainer(buffer, offset, typeArgs, rootNode) {
function writeContainer(value, buffer, offset, typeArgs, rootNode) {
rootNode.this = value;
for (var index in typeArgs.fields) {
if (!value.hasOwnProperty(typeArgs.fields[index].name && typeArgs.fields[index].type != "count" && !typeArgs.fields[index].condition))
debug(new Error("Missing Property " + typeArgs.fields[index].name).stack);
offset = write(value[typeArgs.fields[index].name], buffer, offset, typeArgs.fields[index], rootNode);
}
delete rootNode.this;
@ -1384,6 +1405,7 @@ function get(packetId, state, toServer) {
return packetInfo;
}
// TODO : This does NOT contain the length prefix anymore.
function createPacketBuffer(packetId, state, params, isServer) {
var length = 0;
if (typeof packetId === 'string' && typeof state !== 'string' && !params) {
@ -1400,29 +1422,48 @@ function createPacketBuffer(packetId, state, params, isServer) {
length += sizeOf(params[fieldInfo.name], fieldInfo, params);
});
length += sizeOfVarInt(packetId);
var size = length + sizeOfVarInt(length);
var size = length;// + sizeOfVarInt(length);
var buffer = new Buffer(size);
var offset = writeVarInt(length, buffer, 0);
var offset = 0;//writeVarInt(length, buffer, 0);
offset = writeVarInt(packetId, buffer, offset);
packet.forEach(function(fieldInfo) {
var value = params[fieldInfo.name];
if(typeof value === "undefined") value = 0; // TODO : Why ?
// TODO : A better check is probably needed
if(typeof value === "undefined" && fieldInfo.type != "count" && !fieldInfo.condition)
debug(new Error("Missing Property " + fieldInfo.name).stack);
offset = write(value, buffer, offset, fieldInfo, params);
});
return buffer;
}
function compressPacketBuffer(buffer, callback) {
var dataLength = buffer.length;
var packetLength = dataLength + sizeOfVarInt(dataLength);
var data = zlib.deflateRaw(buffer, function(compressedBuffer) {
var size = sizeOfVarInt(packetLength) + sizeOfVarInt(packetLength) + compressedBuffer.length;
var packetBuffer = new Buffer(size);
var offset = writeVarInt(packetLength, packetBuffer, 0);
offset = writeVarInt(dataLength, packetBuffer, offset);
writeVarInt(compressedBuffer, packetBuffer, offset);
callback(packetBuffer);
});
var dataLength = buffer.size;
zlib.deflateRaw(buffer, function(compressedBuffer) {
var packetLength = sizeOfVarInt(dataLength) + compressedBuffer.length;
var size = sizeOfVarInt(packetLength) + packetLength;
var packetBuffer = new Buffer(size);
var offset = writeVarInt(packetLength, packetBuffer, 0);
offset = writeVarInt(dataLength, packetBuffer, offset);
writeBuffer(compressedBuffer, packetBuffer, offset);
callback(packetBuffer);
});
}
function oldStylePacket(buffer) {
var packet = new Buffer(sizeOfVarInt(buffer.length) + buffer.length);
var cursor = writeVarInt(buffer.length, packet, 0);
writeBuffer(buffer, packet, cursor);
return packet;
}
function newStylePacket(buffer) {
var sizeOfO = sizeOfVarInt(0);
var size = sizeOfVarInt(buffer.length + sizeOfO) + sizeOfO + buffer.length;
var packet = new Buffer(size);
var cursor = writeVarInt(buffer.length, packet, 0);
cursor = writeVarInt(0, packet, cursor);
writeBuffer(buffer, packet, cursor);
return packet;
}
function parsePacket(buffer, state, isServer, packetsToParse) {
@ -1461,7 +1502,8 @@ function parsePacket(buffer, state, isServer, packetsToParse) {
results: results
};
} else {
debug("read packetId " + packetId + " (0x" + packetId.toString(16) + ")");
var packetName = packetNames[state][isServer ? "toServer" : "toClient"][packetId];
debug("read packetId " + state + "." + packetName + " (0x" + packetId.toString(16) + ")");
}
var i, fieldInfo, readResults;
@ -1502,6 +1544,8 @@ module.exports = {
parsePacket: parsePacket,
createPacketBuffer: createPacketBuffer,
compressPacketBuffer: compressPacketBuffer,
oldStylePacket: oldStylePacket,
newStylePacket: newStylePacket,
STRING_MAX_LENGTH: STRING_MAX_LENGTH,
packetIds: packetIds,
packetNames: packetNames,