minecraft.js
Version:
Minecraft data serialization/deserialization and networking
205 lines (170 loc) • 6.12 kB
JavaScript
var net = require('net'),
events = require('events'),
util = require('util');
var Packet = require(__dirname + '/packet.js'),
protocol = require(__dirname + '/protocol.js'),
items = require(__dirname + '/../items.js'),
Item = require(__dirname + '/../item.js');
/** @constructor */
var Socket = module.exports = function() {
events.EventEmitter.call(this);
this._connected = false;
this._writeQueue = [];
this._writeLock = false;
this._isServer = false;
this._readQueue = [];
this._readAppend = false;
this._readLock = false;
this._receivedKicks = false;
this.read = 0;
this.written = 0;
this.socket = new net.Socket();
this.socket.on('error', function(err) {
console.log('error: ' + err);
});
this.socket.on('data', function(data) {
this.socket.pause();
if(this._readAppend) {
this._readQueue[0] = cat(this._readQueue[0], data);
this._readAppend = false;
} else {
this._readQueue.push(data);
}
this.socket.resume();
if(!this._readLock) {
this._readLock = true;
var read = function() {
if(this._readQueue.length > 0) {
if(this._readQueue[0].readUInt8(0) === 0xff
&& this._readQueue[0].readUInt8(1) === 0xff
&& this._readQueue[0].readUInt8(2) === 0xff
&& !this._receivedKicks) {
this._receivedKicks = true;
this._readQueue.shift();
} else {
var packet = this.decode(this._readQueue[0]);
if(packet === true) {
if(this._readQueue.length > 1) {
this.socket.pause();
var head = this._readQueue.shift();
this.socket.resume();
this._readQueue[0] = cat(head, this._readQueue[0]);
} else {
this._readAppend = true;
}
} else if(packet === false) {
var id = this._readQueue[0].readUInt8(0);
this.emit('error', 'Error reading incoming data with id 0x'
+ id.toString(16) + ', (are we out of sync?)');
this.socket.pause();
this._readQueue.shift();
this.socket.resume();
} else {
this.emit('data', packet);
this.emit(packet.id, packet.data);
this.read += packet._size;
if(this._readQueue[0].length > packet._size) {
this._readQueue[0] = this._readQueue[0].slice(packet._size);
} else {
this.socket.pause();
this._readQueue.shift();
this.socket.resume();
}
}
}
process.nextTick(read);
} else {
this._readLock = false;
}
}.bind(this);
read();
}
}.bind(this));
this.socket.on('end', function() {
if(this.connected) {
this.connected = false;
this.emit('end', 'Connection closed by server');
}
}.bind(this));
this.socket.on('drain', function() {
this._writeLock = false;
while(this._writeQueue.length > 0) {
this.write(this._writeQueue.shift());
}
}.bind(this));
};
util.inherits(Socket, events.EventEmitter);
Socket.prototype.write = function(packet, data) {
if( typeof data != 'undefined') {
var format = protocol.get(packet, !this._isServer);
if( typeof format != 'undefined') {
this.write(new Packet(packet, data, !this._isServer));
} else {
this.error('Invalid packet id (0x' + packet.toString(16) + ')');
}
} else {
if(this._writeLock) {
this._writeQueue.push(packet);
} else {
if(!this.socket.write(packet.toString('buffer'))) {
this._writeLock = true;
this._writeQueue.push(packet);
} else {
this.written += packet.getSize();
}
}
}
return this;
};
Socket.prototype.decode = function(buf) {
if(typeof buf !== 'undefined' && buf.length > 0) {
var id = buf.readUInt8(0);
var packet = {};
var format = protocol.get(id, this._isServer);
if(typeof format !== 'undefined') {
try {
buf = buf.slice(1);
var size = 1;
for(var i = 0; i < format.length; i++) {
var unpack = protocol.datatypes[format[i].type.toUpperCase()].unpack(buf);
if(unpack === false) {
return false;
} else {
buf = buf.slice(unpack.bytes);
size += unpack.bytes;
packet[format[i].name] = unpack.value;
}
}
packet = new Packet(id, packet, this._isServer);
packet._size = size;
return packet;
} catch(e) {
return true;
}
} else {
return false;
}
} else {
return true;
}
};
Socket.prototype.connect = function(host, port, callback) {
this.socket.connect(port, host, function() {
this._connected = true;
if(callback) callback();
}.bind(this));
return this;
};
Socket.prototype.destroy = function() {
this.socket.destroy();
};
Socket.prototype.error = function(message) {
this.emit('error', message);
return this;
};
var cat = function(b1, b2) {
var newBuffer = new Buffer(b1.length + b2.length);
b1.copy(newBuffer);
b2.copy(newBuffer, b1.length);
return newBuffer;
};