This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(Minecraft) { | |
/** @constructor */ | |
Minecraft.Client = function(args) { | |
if(Minecraft.ENVIRONMENT == 'node') { | |
if(typeof Minecraft.node.net == 'undefined') Minecraft.node.net = require('net'); | |
if(typeof Minecraft.node.http == 'undefined') Minecraft.node.http = require('http'); | |
if(typeof Minecraft.node.https == 'undefined') Minecraft.node.https = require('https'); | |
if(typeof Minecraft.node.querystring == 'undefined') Minecraft.node.querystring = require('querystring'); | |
this.host = args.host; | |
this.port = args.port || 25565; | |
this.username = args.username || 'Player'; | |
this.password = args.password; | |
this.protocol = args.protocol || 23; | |
this.version = args.version || 12; | |
this.currentVersion; | |
this.downloadTicket; | |
this.sessionId; | |
this._status = 0; | |
this._active = false; | |
this._error = ''; | |
this.socket = new Minecraft.node.net.Socket(); | |
this.socket.on('data', function(data) { | |
var buf = new Buffer(data); | |
if(typeof Minecraft.protocol[buf[0]] != 'undefined') { | |
var packet = Minecraft.protocol[buf[0]].decode(data); | |
var packetData = packet.toObject(); | |
console.log(packet); | |
switch(buf[0]) { | |
case 0x00: | |
if(this._active) { | |
this.send(Minecraft.protocol[0x00].encode(packetData)); | |
} | |
break; | |
case 0x01: | |
if(this._status == 2) this._status = 3; | |
case 0x02: | |
if(this._status == 1) { | |
Minecraft.node.http.get({ | |
host: 'session.minecraft.net', | |
path: '/game/joinserver.jsp?' + | |
Minecraft.node.querystring.stringify({ | |
user: this.username, | |
sessionId: this.sessionId, | |
serverId: packetData.connectionHash | |
}) | |
}, function(res) { | |
res.on('data', function(chunk) { | |
console.log(chunk.toString()); | |
if(chunk.toString() == 'OK') { | |
this._status = 2; | |
this.send(Minecraft.protocol[0x01].encode({ | |
protocolVersion: this.protocol, | |
username: this.username | |
})); | |
} else { | |
this.error(chunk.toString()); | |
} | |
}.bind(this)); | |
}.bind(this)); | |
} | |
break; | |
} | |
} else { | |
console.log('Got unsupported packet (0x' + buf[0].toString(16) + ')'); | |
} | |
}.bind(this)); | |
this.socket.on('end', function() { | |
this.error('Connection closed'); | |
}); | |
} else { | |
throw new Error('Only supported in node'); | |
} | |
}; | |
Minecraft.Client.prototype.login = function(callback) { | |
this._active = true; | |
var postData = Minecraft.node.querystring.stringify({ | |
user: this.username, | |
password: this.password, | |
version: this.version | |
}); | |
var req = Minecraft.node.https.request({ | |
host: 'login.minecraft.net', | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/x-www-form-urlencoded', | |
'Content-Length': postData.length | |
} | |
}, function(res) { | |
res.on('data', function (chunk) { | |
if(this._status == 0) { | |
console.log(chunk.toString()); | |
var split = chunk.toString().split(':'); | |
if(split.length == 4) { | |
this.currentVersion = split[0]; | |
this.downloadTicket = split[1]; | |
this.username = split[2]; | |
this.sessionId = split[3]; | |
this._status = 1; | |
this.socket.connect(this.port, this.host, function() { | |
this.send(Minecraft.protocol[0x02].encode({ | |
username: this.username | |
})); | |
}.bind(this)); | |
var keepAlive = function() { | |
Minecraft.node.https.get({ | |
host: 'login.minecraft.net', | |
path: '/session?' + | |
Minecraft.node.querystring.stringify({ | |
name: this.username, | |
session: this.sessionId | |
}) | |
}); | |
setInterval(keepAlive, 6000 * Minecraft.MS_PER_TICK); | |
}.bind(this); | |
keepAlive(); | |
} else { | |
this.error(chunk.toString()); | |
} | |
} | |
}.bind(this)); | |
}.bind(this)); | |
req.write(postData); | |
}; | |
Minecraft.Client.prototype.send = function(packet) { | |
if(this._active) { | |
this.socket.write(packet.toString()); | |
console.log('>>'); | |
console.log(packet.toString('hex')); | |
} | |
}; | |
Minecraft.Client.prototype.error = function(message) { | |
console.log('ERROR: ' + message + ' (' + this._status + ')'); | |
this._status = -1; | |
this._error = message; | |
this._active = false; | |
this.socket.destroy(); | |
}; | |
})(Minecraft); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(Minecraft) { | |
/** @constructor */ | |
Minecraft.Packet = function(id, data) { | |
if(Minecraft.ENVIRONMENT == 'node') { | |
this.id = id; | |
this.data = data || []; | |
} else { | |
throw new Error('Only supported in node'); | |
} | |
}; | |
Minecraft.Packet.prototype.get = function(key) { | |
if(typeof key == 'number') return this.data[key].value; | |
else if(typeof key == 'string') { | |
for(var i = 0; i < this.data.length; i++) { | |
if(this.data[i].name == key) return this.data[i].value; | |
} | |
} | |
}; | |
Minecraft.Packet.prototype.append = function(name, type, value) { | |
this.data.push({name: name, type: type, value: value}); | |
}; | |
Minecraft.Packet.prototype.toString = function(encoding) { | |
var buf = new Buffer(this.getSize()); | |
var offset = 0; | |
buf.writeInt8(this.id, offset); | |
offset++; | |
for(var i = 0; i < this.data.length; i++) { | |
switch(this.data[i].type.toLowerCase()) { | |
case 'byte': | |
buf.writeInt8(this.data[i].value, offset); | |
break; | |
case 'ubyte': | |
buf.writeUInt8(this.data[i].value, offset); | |
break; | |
case 'short': | |
buf.writeInt16BE(this.data[i].value, offset); | |
break; | |
case 'int': | |
buf.writeInt32BE(this.data[i].value, offset); | |
break; | |
case 'long': | |
buf.writeInt32BE(this.data[i].value >> 4, offset); | |
buf.writeInt32BE(this.data[i].value << 4 >> 4, offset + 4); | |
break; | |
case 'float': | |
buf.writeFloatBE(this.data[i].value, offset); | |
break; | |
case 'double': | |
buf.writeDoubleBE(this.data[i].value, offset); | |
break; | |
case 'string': | |
buf.writeInt16BE(this.data[i].value.length, offset); | |
offset += 2; | |
if(this.data[i].value) { | |
buf.write(this.data[i].value, offset, this.data[i].value.length * 2, 'ucs2'); | |
buf.switchEndian(offset, this.data[i].value.length * 2); | |
} | |
break; | |
case 'bool': | |
buf.writeInt8(this.data[i].value, offset); | |
break; | |
} | |
if(this.data[i].type.toLowerCase() != 'string') { | |
offset += Minecraft.Packet.prototype.types[this.data[i].type.toUpperCase()].size; | |
} else { | |
offset += this.data[i].value.length * 2; | |
} | |
} | |
return buf.toString(encoding || 'utf8'); | |
}; | |
Minecraft.Packet.prototype.toObject = function() { | |
var output = {}; | |
for(var i = 0; i < this.data.length; i++) { | |
output[this.data[i].name] = this.data[i].value; | |
} | |
return output; | |
}; | |
Minecraft.Packet.prototype.getSize = function() { | |
var output = 1; | |
for(var i = 0; i < this.data.length; i++) { | |
if(this.data[i].type.toLowerCase() != 'string') { | |
output += Minecraft.Packet.prototype.types[this.data[i].type.toUpperCase()].size; | |
} else { | |
if(this.data[i].value) output += this.data[i].value.length * 2; | |
output += 2; | |
} | |
} | |
return output; | |
}; | |
Minecraft.Packet.prototype.types = { | |
BYTE: {size: 1}, | |
UBYTE: {size: 1}, | |
SHORT: {size: 2}, | |
INT: {size: 4}, | |
LONG: {size: 8}, | |
FLOAT: {size: 4}, | |
DOUBLE: {size: 8}, | |
STRING: {size: 2}, | |
BOOL: {size: 1} | |
}; | |
})(Minecraft); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(Minecraft) { | |
/** @constructor */ | |
Minecraft.PacketFormat = function(id, toServer, toClient) { | |
if(Minecraft.ENVIRONMENT == 'node') { | |
this.id = id; | |
if(typeof toServer != 'undefined') { | |
if(!Array.isArray(toServer)) { | |
this.toServer = [toServer]; | |
} else { | |
this.toServer = toServer; | |
} | |
} else { | |
throw new Error('Format was not specified'); | |
} | |
if(typeof toClient != 'undefined') { | |
if(!Array.isArray(toClient)) { | |
this.toClient = [toClient]; | |
} else { | |
this.toClient = toClient; | |
} | |
} else { | |
this.toClient = this.toServer; | |
} | |
} else { | |
throw new Error('Only supported in node'); | |
} | |
}; | |
Minecraft.PacketFormat.prototype.encode = function(data, toClient) { | |
var packet = new Minecraft.Packet(this.id); | |
var format = this.toServer; | |
if(toClient) format = this.toClient; | |
for(var i = 0; i < format.length; i++) { | |
if(typeof data[format[i].name] != 'undefined') { | |
packet.append(format[i].name, format[i].type, data[format[i].name]); | |
} else { | |
var value; | |
switch(format[i].type) { | |
case 'string': | |
value = ''; | |
break; | |
case 'bool': | |
value = false; | |
break; | |
default: | |
value = 0; | |
break; | |
} | |
packet.append(format[i].name, format[i].type, value); | |
} | |
} | |
return packet; | |
}; | |
Minecraft.PacketFormat.prototype.decode = function(data, toServer) { | |
var format = this.toClient; | |
if(toServer) format = this.toServer; | |
var buf = new Buffer(data); | |
var packet = new Minecraft.Packet(buf[0]); | |
var offset = 1; | |
for(var i = 0; i < format.length; i++) { | |
var value, stringLength = 0; | |
switch(format[i].type.toLowerCase()) { | |
case 'byte': | |
value = buf.readInt8(offset); | |
break; | |
case 'ubyte': | |
value = buf.readUInt8(offset); | |
break; | |
case 'short': | |
value = buf.readInt16BE(offset); | |
break; | |
case 'int': | |
value = buf.readInt32BE(offset); | |
break; | |
case 'long': | |
//TODO: figure out how to handle 64-bit | |
value = buf.readInt32BE(offset); | |
break; | |
case 'float': | |
value = buf.readFloatBE(offset); | |
break; | |
case 'double': | |
value = buf.readDoubleBE(offset); | |
break; | |
case 'string': | |
stringLength = buf.readInt16BE(offset); | |
offset += 2; | |
buf.switchEndian(offset, stringLength * 2); | |
value = ''; | |
for(var j = 0; j < stringLength * 2; j+=2) { | |
value += String.fromCharCode(buf.readInt8(offset + j) + buf.readInt8(offset + j + 1)); | |
} | |
break; | |
case 'bool': | |
value = buf.readInt8(offset); | |
break; | |
} | |
packet.append(format[i].name, format[i].type, value); | |
if(format[i].type.toLowerCase() != 'string') { | |
offset += Minecraft.Packet.prototype.types[format[i].type.toUpperCase()].size; | |
} else { | |
offset += stringLength * 2; | |
} | |
} | |
return packet; | |
}; | |
})(Minecraft); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(Minecraft) { | |
Minecraft.protocol = { | |
0x00: | |
new Minecraft.PacketFormat(0x00, | |
{type: 'int', name: 'keepAliveId'} | |
), | |
0x01: | |
new Minecraft.PacketFormat(0x01, [ | |
{type: 'int', name: 'protocolVersion'}, | |
{type: 'string', name: 'username'}, | |
{type: 'long', name: 'mapSeed'}, | |
{type: 'string', name: 'levelType'}, | |
{type: 'int', name: 'serverMode'}, | |
{type: 'byte', name: 'dimension'}, | |
{type: 'byte', name: 'difficulty'}, | |
{type: 'ubyte', name: 'worldHeight'}, | |
{type: 'ubyte', name: 'maxPlayers'} | |
]), | |
0x02: | |
new Minecraft.PacketFormat(0x02, | |
//to server | |
{type: 'string', name: 'username'}, | |
//to client | |
{type: 'string', name: 'connectionHash'} | |
) | |
}; | |
})(Minecraft); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment