diff --git a/examples/client_chat.js b/examples/client_chat.js
new file mode 100644
index 0000000..4d7bbb5
--- /dev/null
+++ b/examples/client_chat.js
@@ -0,0 +1,163 @@
+var readline = require('readline');
+var color = require("ansi-color").set;
+var mc = require('../');
+var states = mc.protocol.states;
+var util = require('util');
+
+var colors = new Array();
+colors["black"] = 'black+white_bg';
+colors["dark_blue"] = 'blue';
+colors["dark_green"] = 'green';
+colors["dark_aqua"] = 'cyan'
+colors["dark_red"] = 'red'
+colors["dark_purple"] = 'magenta'
+colors["gold"] = 'yellow'
+colors["gray"] = 'black+white_bg'
+colors["dark_gray"] = 'black+white_bg'
+colors["blue"] = 'blue'
+colors["green"] = 'green'
+colors["aqua"] = 'cyan'
+colors["red"] = 'red'
+colors["light_purple"] = 'magenta'
+colors["yellow"] = 'yellow'
+colors["white"] = 'white'
+colors["obfuscated"] = 'blink'
+colors["bold"] = 'bold'
+colors["strikethrough"] = ''
+colors["underlined"] = 'underlined'
+colors["italic"] = ''
+colors["reset"] = 'white+black_bg'
+
+var dictionary = {};
+dictionary["chat.stream.emote"] = "(%s) * %s %s";
+dictionary["chat.stream.text"] = "(%s) <%s> %s";
+dictionary["chat.type.achievement"] = "%s has just earned the achievement %s";
+dictionary["chat.type.admin"] = "[%s: %s]";
+dictionary["chat.type.announcement"] = "[%s] %s";
+dictionary["chat.type.emote"] = "* %s %s";
+dictionary["chat.type.text"] = "<%s> %s";
+
+var rl = readline.createInterface({
+    input: process.stdin,
+    output: process.stdout,
+    terminal: false
+});
+ 
+function print_help() {
+    console.log("usage: node minechat.js <hostname> <user> <password>");
+}
+ 
+if (process.argv.length < 5) {
+    console.log("Too few arguments!");
+    print_help();
+    process.exit(1);
+}
+ 
+process.argv.forEach(function(val, index, array) {
+    if (val == "-h") {
+        print_help();
+        process.exit(0);
+    }
+});
+ 
+var host = process.argv[2];
+var port = 25565;
+var user = process.argv[3];
+var passwd = process.argv[4];
+ 
+if (host.indexOf(':') != -1) {
+    port = host.substring(host.indexOf(':')+1);
+    host = host.substring(0, host.indexOf(':'));
+}
+ 
+console.log("connecting to " + host + ":" + port);
+console.log("user: " + user);
+console.log("passwd: " + Array(passwd.length).join('*'));
+ 
+var client = mc.createClient({
+    host: host,
+    port: port,
+    username: user,
+    password: passwd
+});
+ 
+client.on([states.PLAY, 0x40], function(packet) {
+    console.info(color('Kicked for ' + packet.reason, "blink+red"));
+    process.exit(1);
+});
+ 
+ var chats = [];
+ 
+client.on('connect', function() {
+    console.info(color('Successfully connected to ' + host + ':' + port, "blink+green"));
+});
+
+client.on('state', function(newState) {
+  if (newState === states.PLAY) {
+    chats.forEach(function(chat) {
+      client.write(0x01, {message: chat});
+    });
+  }
+})
+ 
+rl.on('line', function(line) {
+    if(line == '') {
+        return; 
+    } else if(line == '/quit') {
+        var reason = 'disconnect.quitting';
+        console.info('Disconnected from ' + host + ':' + port);
+        client.write([states.PLAY, 0x40], { reason: reason });	
+        return;
+    } else if(line == '/end') {
+        console.info('Forcibly ended client');
+        process.exit(0);
+        return;
+    }
+    if (!client.write([states.PLAY, 0x01], { message: line })) {
+      chats.push(line);
+    }
+});
+ 
+client.on([states.PLAY, 0x02], function(packet) {
+    var j = JSON.parse(packet.message);
+    var chat = parseChat(j, {});
+    console.info(chat);
+});
+
+function parseChat(chatObj, parentState) {
+  function getColorize(parentState) {
+    var myColor = "";
+    if ('color' in parentState) myColor += colors[parentState.color] + "+";
+    if (parentState.bold) myColor += "bold+";
+    if (parentState.underlined) myColor += "underline+";
+    if (parentState.obfuscated) myColor += "obfuscated+";
+    if (myColor.length > 0) myColor = myColor.slice(0,-1);
+    return myColor;
+  }
+  if (typeof chatObj === "string") {
+    return color(chatObj, getColorize(parentState));
+  } else {
+    var chat = "";
+    if ('color' in chatObj) parentState.color = chatObj['color'];
+    if ('bold' in chatObj) parentState.bold = chatObj['bold'];
+    if ('italic' in chatObj) parentState.italic = chatObj['italic'];
+    if ('underlined' in chatObj) parentState.underlined = chatObj['underlined'];
+    if ('strikethrough' in chatObj) parentState.strikethrough = chatObj['strikethrough'];
+    if ('obfuscated' in chatObj) parentState.obfuscated = chatObj['obfuscated'];
+
+    if ('text' in chatObj) {
+      chat += color(chatObj.text, getColorize(parentState));
+    } else if ('translate' in chatObj && dictionary.hasOwnProperty(chatObj.translate)) {
+      var args = [dictionary[chatObj.translate]];
+      chatObj['with'].forEach(function(s) {
+        args.push(parseChat(s, parentState));
+      });
+
+      chat += color(util.format.apply(this, args), getColorize(parentState));
+    }
+    for (var i in chatObj.extra) {
+      chat += parseChat(chatObj.extra[i], parentState);
+    }
+    return chat;
+  }
+}
\ No newline at end of file
diff --git a/examples/client_echo.js b/examples/client_echo.js
index 2657f75..7f6192b 100644
--- a/examples/client_echo.js
+++ b/examples/client_echo.js
@@ -1,4 +1,6 @@
-var mc = require('../');
+var mc = require('../')
+  , states = mc.protocol.states
+
 var client = mc.createClient({
   username: process.env.MC_USERNAME,
   password: process.env.MC_PASSWORD,
@@ -6,12 +8,12 @@ var client = mc.createClient({
 client.on('connect', function() {
   console.info('connected');
 });
-client.on(0x03, function(packet) {
+client.on([states.PLAY, 0x02], function(packet) {
   var jsonMsg = JSON.parse(packet.message);
   if (jsonMsg.translate == 'chat.type.announcement' || jsonMsg.translate == 'chat.type.text') {
-    var username = jsonMsg.using[0];
-    var msg = jsonMsg.using[1];
+    var username = jsonMsg.with[0];
+    var msg = jsonMsg.with[1];
     if (username === client.username) return;
-    client.write(0x03, {message: msg});
+    client.write(0x01, {message: msg});
   }
-});
+});
\ No newline at end of file
diff --git a/examples/server.js b/examples/server.js
index 17af12a..23bcb0f 100644
--- a/examples/server.js
+++ b/examples/server.js
@@ -1,4 +1,5 @@
 var mc = require('../');
+var states = mc.protocol.states;
 
 var yellow = '§e';
 
@@ -30,17 +31,16 @@ server.on('login', function(client) {
     difficulty: 2,
     maxPlayers: server.maxPlayers
   });
-  client.write(0x0d, {
+  client.write(0x08, {
     x: 0,
     y: 256,
-    stance: 255,
     z: 0,
     yaw: 0,
     pitch: 0,
     onGround: true
   });
 
-  client.on(0x03, function(data) {
+  client.on([states.PLAY, 0x01], function(data) {
     var message = '<'+client.username+'>' + ' ' + data.message;
     broadcast(message, client, client.username);
     console.log(message);
@@ -66,12 +66,12 @@ function broadcast(message, exclude, username) {
     if (client !== exclude) {
       var msg = {
         translate: translate,
-        using: [
+        "with": [
           username,
           'Hello, world!'
         ]
       };
-      client.write(0x03, { message: JSON.stringify(msg) });
+      client.write(0x02, { message: JSON.stringify(msg) });
     }
   }
 }
diff --git a/examples/server_helloworld.js b/examples/server_helloworld.js
index ed7191c..42d787d 100644
--- a/examples/server_helloworld.js
+++ b/examples/server_helloworld.js
@@ -1,7 +1,7 @@
 var mc = require('../');
 
 var options = {
-  'online-mode': false, // optional
+ // 'online-mode': false, // optional
 };
 
 var server = mc.createServer(options);
@@ -23,10 +23,9 @@ server.on('login', function(client) {
     difficulty: 2,
     maxPlayers: server.maxPlayers
   });
-  client.write(0x0d, {
+  client.write(0x08, {
     x: 0,
     y: 1.62,
-    stance: 0,
     z: 0,
     yaw: 0,
     pitch: 0,
@@ -35,12 +34,12 @@ server.on('login', function(client) {
 
   var msg = {
     translate: 'chat.type.announcement',
-    using: [
+    "with": [
       'Server',
       'Hello, world!'
     ]
   };
-  client.write(0x03, { message: JSON.stringify(msg) });
+  client.write(0x02, { message: JSON.stringify(msg) });
 });
 
 server.on('error', function(error) {
diff --git a/index.js b/index.js
index 9af2522..6962772 100644
--- a/index.js
+++ b/index.js
@@ -1,15 +1,20 @@
 var EventEmitter = require('events').EventEmitter
-  , util = require('util')
-  , assert = require('assert')
-  , ursa = require('ursa')
-  , crypto = require('crypto')
-  , bufferEqual = require('buffer-equal')
-  , superagent = require('superagent')
-  , protocol = require('./lib/protocol')
-  , Client = require('./lib/client')
-  , Server = require('./lib/server')
-  , debug = protocol.debug
-;
+        , util = require('util')
+        , assert = require('assert')
+        , ursa = require('ursa')
+        , crypto = require('crypto')
+        , bufferEqual = require('buffer-equal')
+        , superagent = require('superagent')
+        , protocol = require('./lib/protocol')
+        , Client = require('./lib/client')
+        , Server = require('./lib/server')
+        , Yggdrasil = require('./lib/yggdrasil.js')
+        , getSession = Yggdrasil.getSession
+        , validateSession = Yggdrasil.validateSession
+        , joinServer = Yggdrasil.joinServer
+        , states = protocol.states
+        , debug = protocol.debug
+        ;
 
 module.exports = {
   createClient: createClient,
@@ -23,10 +28,10 @@ module.exports = {
 function createServer(options) {
   options = options || {};
   var port = options.port != null ?
-    options.port :
-    options['server-port'] != null ?
-      options['server-port'] :
-      25565 ;
+          options.port :
+          options['server-port'] != null ?
+          options['server-port'] :
+          25565;
   var host = options.host || '0.0.0.0';
   var kickTimeout = options.kickTimeout || 10 * 1000;
   var checkTimeoutInterval = options.checkTimeoutInterval || 4 * 1000;
@@ -41,9 +46,9 @@ function createServer(options) {
   server.playerCount = 0;
   server.onlineModeExceptions = {};
   server.on("connection", function(client) {
-    client.once(0xfe, onPing);
-    client.once(0x02, onHandshake);
-    client.once(0xFC, onEncryptionKeyResponse);
+    client.once([states.HANDSHAKING, 0x00], onHandshake);
+    client.once([states.LOGIN, 0x00], onLogin);
+    client.once([states.STATUS, 0x00], onPing);
     client.on('end', onEnd);
 
     var keepAlive = false;
@@ -60,7 +65,8 @@ function createServer(options) {
     }
 
     function keepAliveLoop() {
-      if (! keepAlive) return;
+      if (!keepAlive)
+        return;
 
       // check if the last keepAlive was too long ago (kickTimeout)
       var elapsed = new Date() - lastKeepAlive;
@@ -90,40 +96,44 @@ function createServer(options) {
     }
 
     function onPing(packet) {
-      if (loggedIn) return;
-      client.write(0xff, {
-        reason: [
-          '§1',
-          protocol.version,
-          protocol.minecraftVersion,
-          server.motd,
-          server.playerCount,
-          server.maxPlayers,
-        ].join('\u0000')
+      var response = {
+        "version": {
+          "name": protocol.minecraftVersion,
+          "protocol": protocol.version
+        },
+        "players": {
+          "max": server.maxPlayers,
+          "online": server.playerCount,
+          "sample": []
+        },
+        "description": {"text": server.motd},
+        "favicon": server.favicon
+      };
+
+      client.once([states.STATUS, 0x01], function(packet) {
+        client.write(0x01, { time: packet.time });
+        client.end();
       });
+      client.write(0x00, {response: JSON.stringify(response)});
     }
 
-    function onHandshake(packet) {
+    function onLogin(packet) {
       client.username = packet.username;
       var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
       var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException);
-      var serverId;
-      if (needToVerify) {
-        serverId = crypto.randomBytes(4).toString('hex');
-      } else {
-        serverId = '-';
-      }
-      if (encryptionEnabled) {
+      if (encryptionEnabled || needToVerify) {
+        var serverId = crypto.randomBytes(4).toString('hex');
         client.verifyToken = crypto.randomBytes(4);
         var publicKeyStrArr = serverKey.toPublicPem("utf8").split("\n");
         var publicKeyStr = "";
-        for (var i=1;i<publicKeyStrArr.length - 2;i++) {
+        for (var i = 1; i < publicKeyStrArr.length - 2; i++) {
           publicKeyStr += publicKeyStrArr[i]
         }
-        client.publicKey = new Buffer(publicKeyStr,'base64');
+        client.publicKey = new Buffer(publicKeyStr, 'base64');
         hash = crypto.createHash("sha1");
         hash.update(serverId);
-        client.write(0xFD, {
+        client.once([states.LOGIN, 0x01], onEncryptionKeyResponse);
+        client.write(0x01, {
           serverId: serverId,
           publicKey: client.publicKey,
           verifyToken: client.verifyToken
@@ -133,9 +143,17 @@ function createServer(options) {
       }
     }
 
+    function onHandshake(packet) {
+      if (packet.nextState == 1) {
+        client.state = states.STATUS;
+      } else if (packet.nextState == 2) {
+        client.state = states.LOGIN;
+      }
+    }
+
     function onEncryptionKeyResponse(packet) {
       var verifyToken = serverKey.decrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
-      if (! bufferEqual(client.verifyToken, verifyToken)) {
+      if (!bufferEqual(client.verifyToken, verifyToken)) {
         client.end('DidNotEncryptVerifyTokenProperly');
         return;
       }
@@ -144,49 +162,29 @@ function createServer(options) {
       client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
       hash.update(sharedSecret);
       hash.update(client.publicKey);
-      client.write(0xFC, {
-        sharedSecret: new Buffer(0),
-        verifyToken: new Buffer(0)
-      });
       client.encryptionEnabled = true;
 
       var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
-      var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException);
+      var needToVerify = (onlineMode && !isException) || (!onlineMode && isException);
       var nextStep = needToVerify ? verifyUsername : loginClient;
       nextStep();
 
       function verifyUsername() {
         var digest = mcHexDigest(hash);
-        var request = superagent.get("http://session.minecraft.net/game/checkserver.jsp");
-        request.query({
-          user: client.username,
-          serverId: digest
-        });
-        request.end(function(err, resp) {
-          var myErr;
+        validateSession(client.username, digest, function(err, uuid) {
           if (err) {
-            server.emit('error', err);
-            client.end('McSessionUnavailable');
-          } else if (resp.serverError) {
-            myErr = new Error("session.minecraft.net is broken: " + resp.status);
-            myErr.code = 'EMCSESSION500';
-            server.emit('error', myErr);
-            client.end('McSessionDown');
-          } else if (resp.serverError) {
-            myErr = new Error("session.minecraft.net rejected request: " + resp.status);
-            myErr.code = 'EMCSESSION400';
-            server.emit('error', myErr);
-            client.end('McSessionRejectedAuthRequest');
-          } else if (resp.text !== "YES") {
-            client.end('FailedToVerifyUsername');
-          } else {
-            loginClient();
+            client.end("Failed to verify username!");
+            return;
           }
+          client.UUID = uuid;
+          loginClient();
         });
       }
     }
 
     function loginClient() {
+      client.write(0x02, {uuid: (client.UUID | 0).toString(10), username: client.username});
+      client.state = states.PLAY;
       loggedIn = true;
       startKeepAlive();
 
@@ -208,28 +206,36 @@ function createClient(options) {
   assert.ok(options, "options is required");
   var port = options.port || 25565;
   var host = options.host || 'localhost';
+  var clientToken = options.clientToken || Yggdrasil.generateUUID();
+  var accessToken = options.accessToken || null;
+
   assert.ok(options.username, "username is required");
-  var haveCredentials = options.password != null;
+  var haveCredentials = options.password != null || (clientToken != null && accessToken != null);
   var keepAlive = options.keepAlive == null ? true : options.keepAlive;
 
+
   var client = new Client(false);
   client.on('connect', onConnect);
-  if (keepAlive) client.on(0x00, onKeepAlive);
-  client.once(0xFC, onEncryptionKeyResponse);
-  client.once(0xFD, onEncryptionKeyRequest);
+  if (keepAlive) client.on([states.PLAY, 0x00], onKeepAlive);
+  client.once([states.LOGIN, 0x01], onEncryptionKeyRequest);
+  client.once([states.LOGIN, 0x02], onLogin);
 
   if (haveCredentials) {
     // make a request to get the case-correct username before connecting.
-    getLoginSession(options.username, options.password, function(err, session) {
+    var cb = function(err, session) {
       if (err) {
         client.emit('error', err);
       } else {
         client.session = session;
         client.username = session.username;
+        accessToken = session.accessToken;
         client.emit('session');
         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 {
     // assume the server is in offline mode and just go for it.
     client.username = options.username;
@@ -239,11 +245,16 @@ function createClient(options) {
   return client;
 
   function onConnect() {
-    client.write(0x02, {
+    client.write(0x00, {
       protocolVersion: protocol.version,
-      username: client.username,
       serverHost: host,
       serverPort: port,
+      nextState: 2
+    });
+
+    client.state = states.LOGIN;
+    client.write(0x00, {
+      username: client.username
     });
   }
 
@@ -288,28 +299,7 @@ function createClient(options) {
         hash.update(packet.publicKey);
 
         var digest = mcHexDigest(hash);
-        var request = superagent.get("http://session.minecraft.net/game/joinserver.jsp");
-        request.query({
-          user: client.session.username,
-          sessionId: client.session.id,
-          serverId: digest,
-        });
-        request.end(function(err, resp) {
-          var myErr;
-          if (err) {
-            cb(err);
-          } else if (resp.serverError) {
-            myErr = new Error("session.minecraft.net is broken: " + resp.status);
-            myErr.code = 'EMCSESSION500';
-            cb(myErr);
-          } else if (resp.clientError) {
-            myErr = new Error("session.minecraft.net rejected request: " + resp.status + " " + resp.text);
-            myErr.code = 'EMCSESSION400';
-            cb(myErr);
-          } else {
-            cb();
-          }
-        });
+        joinServer(this.username, digest, accessToken, client.session.selectedProfile.id, cb);
       }
 
       function sendEncryptionKeyResponse() {
@@ -318,19 +308,19 @@ function createClient(options) {
         var encryptedVerifyTokenBuffer = pubKey.encrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
         client.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
         client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
-        client.write(0xfc, {
+        client.write(0x01, {
           sharedSecret: encryptedSharedSecretBuffer,
           verifyToken: encryptedVerifyTokenBuffer,
         });
+        client.encryptionEnabled = true;
       }
     }
   }
-
-  function onEncryptionKeyResponse(packet) {
-    assert.strictEqual(packet.sharedSecret.length, 0);
-    assert.strictEqual(packet.verifyToken.length, 0);
-    client.encryptionEnabled = true;
-    client.write(0xcd, { payload: 0 });
+  
+  function onLogin(packet) {
+    client.state = states.PLAY;
+    client.uuid = packet.uuid;
+    client.username = packet.username;
   }
 }
 
@@ -352,11 +342,13 @@ function mcHexDigest(hash) {
   var buffer = new Buffer(hash.digest(), 'binary');
   // check for negative hashes
   var negative = buffer.readInt8(0) < 0;
-  if (negative) performTwosCompliment(buffer);
+  if (negative)
+    performTwosCompliment(buffer);
   var digest = buffer.toString('hex');
   // trim leading zeroes
   digest = digest.replace(/^0+/g, '');
-  if (negative) digest = '-' + digest;
+  if (negative)
+    digest = '-' + digest;
   return digest;
 
   function performTwosCompliment(buffer) {
@@ -374,42 +366,3 @@ function mcHexDigest(hash) {
     }
   }
 }
-
-function getLoginSession(email, password, cb) {
-  var req = superagent.post("https://login.minecraft.net");
-  req.type('form');
-  req.send({
-    user: email,
-    password: password,
-    version: protocol.sessionVersion,
-  });
-  req.end(function(err, resp) {
-    var myErr;
-    if (err) {
-      cb(err);
-    } else if (resp.serverError) {
-      myErr = new Error("login.minecraft.net is broken: " + resp.status);
-      myErr.code = 'ELOGIN500';
-      cb(myErr);
-    } else if (resp.clientError) {
-      myErr = new Error("login.minecraft.net rejected request: " + resp.status + " " + resp.text);
-      myErr.code = 'ELOGIN400';
-      cb(myErr);
-    } else {
-      var values = resp.text.split(':');
-      var session = {
-        currentGameVersion: values[0],
-        username: values[2],
-        id: values[3],
-        uid: values[4],
-      };
-      if (session.id && session.username) {
-        cb(null, session);
-      } else {
-        myErr = new Error("login.minecraft.net rejected request: " + resp.status + " " + resp.text);
-        myErr.code = 'ELOGIN400';
-        cb(myErr);
-      }
-    }
-  });
-}
diff --git a/lib/client.js b/lib/client.js
index 09424e9..b2981b7 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -5,6 +5,7 @@ var net = require('net')
   , dns = require('dns')
   , createPacketBuffer = protocol.createPacketBuffer
   , parsePacket = protocol.parsePacket
+  , states = protocol.states
   , debug = protocol.debug
 ;
 
@@ -13,6 +14,17 @@ module.exports = Client;
 function Client(isServer) {
   EventEmitter.call(this);
 
+  this._state = states.HANDSHAKING;
+  Object.defineProperty(this, "state", {
+    get: function() {
+      return this._state;
+    },
+    set: function(newProperty) {
+      var oldProperty = this._state;
+      this._state = newProperty;
+      this.emit('state', newProperty, oldProperty);
+    }
+  });
   this.isServer = !!isServer;
   this.socket = null;
   this.encryptionEnabled = false;
@@ -30,7 +42,7 @@ Client.prototype.setSocket = function(socket) {
     incomingBuffer = Buffer.concat([incomingBuffer, data]);
     var parsed, packet;
     while (true) {
-      parsed = parsePacket(incomingBuffer, self.isServer);
+      parsed = parsePacket(incomingBuffer, self.state, self.isServer);
       if (! parsed) break;
       if (parsed.error) {
           this.emit('error', parsed.error);
@@ -39,6 +51,7 @@ Client.prototype.setSocket = function(socket) {
       }
       packet = parsed.results;
       incomingBuffer = incomingBuffer.slice(parsed.size);
+      self.emit([self.state, packet.id], packet);
       self.emit(packet.id, packet);
       self.emit('packet', packet);
     }
@@ -78,7 +91,7 @@ Client.prototype.connect = function(port, host) {
       } else {
         self.setSocket(net.connect(port, host));
       }
-    });  
+    });
   } else {
     self.setSocket(net.connect(port, host));
   }
@@ -90,9 +103,16 @@ Client.prototype.end = function(reason) {
 };
 
 Client.prototype.write = function(packetId, params) {
-  var buffer = createPacketBuffer(packetId, params, this.isServer);
+  if (Array.isArray(packetId)) {
+     if (packetId[0] !== this.state)
+      return false;
+    packetId = packetId[1];
+  }
+  
+  var buffer = createPacketBuffer(packetId, this.state, params, this.isServer);
   debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")");
   debug(params);
   var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
   this.socket.write(out);
+  return true;
 };
diff --git a/lib/ping.js b/lib/ping.js
index 8390c93..d4332dc 100644
--- a/lib/ping.js
+++ b/lib/ping.js
@@ -1,6 +1,7 @@
 var net = require('net')
   , Client = require('./client')
   , protocol = require('./protocol')
+  , states = protocol.states
 ;
 
 module.exports = ping;
@@ -10,42 +11,35 @@ function ping(options, cb) {
   var port = options.port || 25565;
 
   var client = new Client();
-  client.once(0xff, function(packet) {
-    var parts = packet.reason.split('\u0000');
-    var results;
-    try {
-      results = {
-        prefix: parts[0],
-        protocol: parseInt(parts[1], 10),
-        version: parts[2],
-        motd: parts[3],
-        playerCount: parseInt(parts[4], 10),
-        maxPlayers: parseInt(parts[5], 10),
-        latency: Date.now() - start
-      };
-    } catch (err) {
-      client.end();
-      cb(err);
-      return;
-    }
-    client.end();
-    cb(null, results);
-  });
   client.on('error', function(err) {
     cb(err);
   });
-  client.on('connect', function() {
-    client.write(0xfe, {
-      readSuccessfully: 1,
-      customPayloadId: 250,
-      magicText: "MC|PingHost",
-      len: 3 + host.length + 4,
-      version: protocol.version,
-      ip: host,
-      port: port,
+  
+  client.once([states.STATUS, 0x00], function(packet) {
+    var data = JSON.parse(packet.response);
+    var start = Date.now();
+    client.once(0x01, function(packet) {
+      data.latency = Date.now() - start;
+      cb(null, data);
+      client.end();
     });
+    client.write(0x01, { time: [0, 0]});
+  });
+
+  client.on('state', function(newState) {
+    if (newState === states.STATUS)
+      client.write(0x00, {});
+  });
+  
+  client.on('connect', function() {
+    client.write(0x00, {
+      protocolVersion: 4,
+      serverHost: host,
+      serverPort: port,
+      nextState: 1
+    });
+    client.state = states.STATUS;
   });
   
-  var start = Date.now();
   client.connect(port, host);
 }
diff --git a/lib/protocol.js b/lib/protocol.js
index d3cc390..357aaa6 100644
--- a/lib/protocol.js
+++ b/lib/protocol.js
@@ -4,522 +4,591 @@ var util = require('util');
 var STRING_MAX_LENGTH = 240;
 var SRV_STRING_MAX_LENGTH = 32767;
 
+// This is really just for the client.
+var states = {
+  "HANDSHAKING": "handshaking",
+  "STATUS": "status",
+  "LOGIN": "login",
+  "PLAY": "play"
+}
+
 var packets = {
-  0x00: [
-    { name: "keepAliveId", type: "int" }
-  ],
-  0x01: [
-    { name: "entityId", type: "int" },
-    { name: "levelType", type: "string" },
-    { name: "gameMode", type: "byte" },
-    { name: "dimension", type: "byte" },
-    { name: "difficulty", type: "byte" },
-    { name: null, type: "byte" },
-    { name: "maxPlayers", type: "byte" }
-  ],
-  0x02: [
-    { name: "protocolVersion", type: "byte" },
-    { name: "username", type: "string" },
-    { name: "serverHost", type: "string" },
-    { name: "serverPort", type: "int" }
-  ],
-  0x03: {
-    toServer: [
-      { name: "message", type: "string" }
-    ],
-    toClient: [
-      { name: "message", type: "ustring" }
-    ]
+  "handshaking": {
+    "toServer": {
+      0x00: [
+        { name: "protocolVersion", type: "varint" },
+        { name: "serverHost", type: "string" },
+        { name: "serverPort", type: "ushort" },
+        { name: "nextState", type: "varint" }
+      ]
+    }
   },
-  0x04: [
-    { name: "age", type: "long" },
-    { name: "time", type: "long" }
-  ],
-  0x05: [
-    { name: "entityId", type: "int" },
-    { name: "slot", type: "short" },
-    { name: "item", type: "slot" }
-  ],
-  0x06: [
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" }
-  ],
-  0x07: [
-    { name: "user", type: "int" },
-    { name: "target", type: "int" },
-    { name: "leftClick", type: "bool" }
-  ],
-  0x08: [
-    { name: "health", type: "float" },
-    { name: "food", type: "short" },
-    { name: "foodSaturation", type: "float" }
-  ],
-  0x09: [
-    { name: "dimension", type: "int" },
-    { name: "difficulty", type: "byte" },
-    { name: "gameMode", type: "byte" },
-    { name: "worldHeight", type: "short" },
-    { name: "levelType", type: "string" }
-  ],
-  0x0a: [
-    { name: "onGround", type: "bool" }
-  ],
-  0x0b: [
-    { name: "x", type: "double" },
-    { name: "y", type: "double" },
-    { name: "stance", type: "double" },
-    { name: "z", type: "double" },
-    { name: "onGround", type: "bool" }
-  ],
-  0x0c: [
-    { name: "yaw", type: "float" },
-    { name: "pitch", type: "float" },
-    { name: "onGround", type: "bool" }
-  ],
-  0x0d: {
-    toServer: [
-      { name: "x", type: "double" },
-      { name: "y", type: "double" },
-      { name: "stance", type: "double" },
-      { name: "z", type: "double" },
-      { name: "yaw", type: "float" },
-      { name: "pitch", type: "float" },
-      { name: "onGround", type: "bool" }
-    ],
-    toClient: [
-      { name: "x", type: "double" },
-      { name: "stance", type: "double" },
-      { name: "y", type: "double" },
-      { name: "z", type: "double" },
-      { name: "yaw", type: "float" },
-      { name: "pitch", type: "float" },
-      { name: "onGround", type: "bool" }
-    ],
+  "status": {
+    "toClient": {
+      0x00: [
+        { name: "response", type: "string" }
+      ],
+      0x01: [
+        { name: "time", type: "long" }
+      ]
+    },
+    "toServer": {
+      0x00: [],
+      0x01: [
+        { name: "time", type: "long" }
+      ]
+    }
   },
-  0x0e: [
-    { name: "status", type: "byte" },
-    { name: "x", type: "int" },
-    { name: "y", type: "ubyte" },
-    { name: "z", type: "int" },
-    { name: "face", type: "ubyte" }
-  ],
-  0x0f: [
-    { name: "x", type: "int" },
-    { name: "y", type: "ubyte" },
-    { name: "z", type: "int" },
-    { name: "direction", type: "byte" },
-    { name: "heldItem", type: "slot" },
-    { name: "cursorX", type: "byte" },
-    { name: "cursorY", type: "byte" },
-    { name: "cursorZ", type: "byte" }
-  ],
-  0x10: [
-    { name: "slotId", type: "short" }
-  ],
-  0x11: [
-    { name: "entityId", type: "int" },
-    { name: null, type: "byte" },
-    { name: "x", type: "int" },
-    { name: "y", type: "ubyte" },
-    { name: "z", type: "int" }
-  ],
-  0x12: [
-    { name: "entityId", type: "int" },
-    { name: "animation", type: "byte" }
-  ],
-  0x13: [
-    { name: "entityId", type: "int" },
-    { name: "actionId", type: "byte" },
-    { name: "jumpBoost", type: "int" }
-  ],
-  0x14: [
-    { name: "entityId", type: "int" },
-    { name: "name", type: "string" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "yaw", type: "byte" },
-    { name: "pitch", type: "byte" },
-    { name: "currentItem", type: "short" },
-    { name: "metadata", type: "entityMetadata" }
-  ],
-  0x16: [
-    { name: "collectedId", type: "int" },
-    { name: "collectorId", type: "int" }
-  ],
-  0x17: [
-    { name: "entityId", type: "int" },
-    { name: "type", type: "byte" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "yaw", type: "byte" },
-    { name: "pitch", type: "byte" },
-    { name: "objectData", type: "objectData" }
-  ],
-  0x18: [
-    { name: "entityId", type: "int" },
-    { name: "type", type: "byte" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "yaw", type: "byte" },
-    { name: "pitch", type: "byte" },
-    { name: "headYaw", type: "byte" },
-    { name: "velocityX", type: "short" },
-    { name: "velocityY", type: "short" },
-    { name: "velocityZ", type: "short" },
-    { name: "metadata", type: "entityMetadata" }
-  ],
-  0x19: [
-    { name: "entityId", type: "int" },
-    { name: "name", type: "string" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "direction", type: "int" }
-  ],
-  0x1a: [
-    { name: "entityId", type: "int" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "count", type: "short" }
-  ],
-  0x1b: [
-    { name: "sideways", type: "float" },
-    { name: "forward", type: "float" },
-    { name: "jump", type: "bool" },
-    { name: "unmount", type: "bool" }
-  ],
-  0x1c: [
-    { name: "entityId", type: "int" },
-    { name: "velocityX", type: "short" },
-    { name: "velocityY", type: "short" },
-    { name: "velocityZ", type: "short" }
-  ],
-  0x1d: [
-    { name: "entityIds", type: "intArray8" }
-  ],
-  0x1e: [
-    { name: "entityId", type: "int" }
-  ],
-  0x1f: [
-    { name: "entityId", type: "int" },
-    { name: "dx", type: "byte" },
-    { name: "dy", type: "byte" },
-    { name: "dz", type: "byte" }
-  ],
-  0x20: [
-    { name: "entityId", type: "int" },
-    { name: "yaw", type: "byte" },
-    { name: "pitch", type: "byte" }
-  ],
-  0x21: [
-    { name: "entityId", type: "int" },
-    { name: "dx", type: "byte" },
-    { name: "dy", type: "byte" },
-    { name: "dz", type: "byte" },
-    { name: "yaw", type: "byte" },
-    { name: "pitch", type: "byte" }
-  ],
-  0x22: [
-    { name: "entityId", type: "int" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "yaw", type: "byte" },
-    { name: "pitch", type: "byte" }
-  ],
-  0x23: [
-    { name: "entityId", type: "int" },
-    { name: "headYaw", type: "byte" }
-  ],
-  0x26: [
-    { name: "entityId", type: "int" },
-    { name: "status", type: "byte" }
-  ],
-  0x27: [
-    { name: "entityId", type: "int" },
-    { name: "vehicleId", type: "int" },
-    { name: "leash", type: "ubyte" }
-  ],
-  0x28: [
-    { name: "entityId", type: "int" },
-    { name: "metadata", type: "entityMetadata" }
-  ],
-  0x29: [
-    { name: "entityId", type: "int" },
-    { name: "effectId", type: "byte" },
-    { name: "amplifier", type: "byte" },
-    { name: "duration", type: "short" }
-  ],
-  0x2a: [
-    { name: "entityId", type: "int" },
-    { name: "effectId", type: "byte" }
-  ],
-  0x2b: [
-    { name: "experienceBar", type: "float" },
-    { name: "level", type: "short" },
-    { name: "totalExperience", type: "short" }
-  ],
-  0x2c: [
-    { name: "entityId", type: "int" },
-    { name: "properties", type: "propertyArray" }
-  ],
-  0x33: [
-    { name: "x", type: "int" },
-    { name: "z", type: "int" },
-    { name: "groundUp", type: "bool" },
-    { name: "bitMap", type: "ushort" },
-    { name: "addBitMap", type: "ushort" },
-    { name: "compressedChunkData", type: "byteArray32" }
-  ],
-  0x34: [
-    { name: "chunkX", type: "int" },
-    { name: "chunkZ", type: "int" },
-    { name: "recordCount", type: "short" },
-    { name: "data", type: "byteArray32" }
-  ],
-  0x35: [
-    { name: "x", type: "int" },
-    { name: "y", type: "ubyte" },
-    { name: "z", type: "int" },
-    { name: "type", type: "short" },
-    { name: "metadata", type: "byte" }
-  ],
-  0x36: [
-    { name: "x", type: "int" },
-    { name: "y", type: "short" },
-    { name: "z", type: "int" },
-    { name: "byte1", type: "byte" },
-    { name: "byte2", type: "byte" },
-    { name: "blockId", type: "short" }
-  ],
-  0x37: [
-    { name: "entityId", type: "int" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "destroyStage", type: "byte" }
-  ],
-  0x38: [
-    { name: "data", type: "mapChunkBulk" }
-  ],
-  0x3c: [
-    { name: "x", type: "double" },
-    { name: "y", type: "double" },
-    { name: "z", type: "double" },
-    { name: "radius", type: "float" },
-    { name: "affectedBlockOffsets", type: "byteVectorArray" },
-    { name: "playerMotionX", type: "float" },
-    { name: "playerMotionY", type: "float" },
-    { name: "playerMotionZ", type: "float" }
-  ],
-  0x3d: [
-    { name: "effectId", type: "int" },
-    { name: "x", type: "int" },
-    { name: "y", type: "ubyte" },
-    { name: "z", type: "int" },
-    { name: "data", type: "int" },
-    { name: "global", type: "bool" }
-  ],
-  0x3e: [
-    { name: "soundName", type: "string" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" },
-    { name: "volume", type: "float" },
-    { name: "pitch", type: "byte" }
-  ],
-  0x3f: [
-    { name: "particleName", type: "string" },
-    { name: "x", type: "float" },
-    { name: "y", type: "float" },
-    { name: "z", type: "float" },
-    { name: "offsetX", type: "float" },
-    { name: "offsetY", type: "float" },
-    { name: "offsetZ", type: "float" },
-    { name: "particleSpeed", type: "float" },
-    { name: "particles", type: "int" }
-  ],
-  0x46: [
-    { name: "reason", type: "byte" },
-    { name: "gameMode", type: "byte" }
-  ],
-  0x47: [
-    { name: "entityId", type: "int" },
-    { name: "type", type: "byte" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" }
-  ],
-  0x64: [
-    { name: "windowId", type: "byte" },
-    { name: "inventoryType", type: "byte" },
-    { name: "windowTitle", type: "string" },
-    { name: "slotCount", type: "byte" },
-    { name: "useProvidedTitle", type: "bool" },
-    { name: "entityId", type: "int", condition: function(field_values) {
-      return field_values['inventoryType'] == 11;
-    } }
-  ],
-  0x65: [
-    { name: "windowId", type: "byte" }
-  ],
-  0x66: [
-    { name: "windowId", type: "byte" },
-    { name: "slot", type: "short" },
-    { name: "mouseButton", type: "byte" },
-    { name: "action", type: "short" },
-    { name: "mode", type: "byte" },
-    { name: "item", type: "slot" }
-  ],
-  0x67: [
-    { name: "windowId", type: "byte" },
-    { name: "slot", type: "short" },
-    { name: "item", type: "slot" }
-  ],
-  0x68: [
-    { name: "windowId", type: "byte" },
-    { name: "items", type: "slotArray" }
-  ],
-  0x69: [
-    { name: "windowId", type: "byte" },
-    { name: "property", type: "short" },
-    { name: "value", type: "short" }
-  ],
-  0x6a: [
-    { name: "windowId", type: "byte" },
-    { name: "action", type: "short" },
-    { name: "accepted", type: "bool" }
-  ],
-  0x6b: [
-    { name: "slot", type: "short" },
-    { name: "item", type: "slot" }
-  ],
-  0x6c: [
-    { name: "windowId", type: "byte" },
-    { name: "enchantment", type: "byte" }
-  ],
-  0x82: [
-    { name: "x", type: "int" },
-    { name: "y", type: "short" },
-    { name: "z", type: "int" },
-    { name: "text1", type: "string" },
-    { name: "text2", type: "string" },
-    { name: "text3", type: "string" },
-    { name: "text4", type: "string" }
-  ],
-  0x83: [
-    { name: "type", type: "short" },
-    { name: "itemId", type: "short" },
-    { name: "text", type: "ascii" }
-  ],
-  0x84: [
-    { name: "x", type: "int" },
-    { name: "y", type: "short" },
-    { name: "z", type: "int" },
-    { name: "action", type: "byte" },
-    { name: "nbtData", type: "byteArray16" }
-  ],
-  0x85: [
-    { name: "tileEntityId", type: "byte" },
-    { name: "x", type: "int" },
-    { name: "y", type: "int" },
-    { name: "z", type: "int" }
-  ],
-  0xc8: [
-    { name: "statisticId", type: "int" },
-    { name: "amount", type: "int" }
-  ],
-  0xc9: [
-    { name: "playerName", type: "string" },
-    { name: "online", type: "bool" },
-    { name: "ping", type: "short" }
-  ],
-  0xca: [
-    { name: "flags", type: "byte" },
-    { name: "flyingSpeed", type: "float" },
-    { name: "walkingSpeed", type: "float" }
-  ],
-  0xcb: [
-    { name: "text", type: "string" }
-  ],
-  0xcc: [
-    { name: "locale", type: "string" },
-    { name: "viewDistance", type: "byte" },
-    { name: "chatFlags", type: "byte" },
-    { name: "difficulty", type: "byte" },
-    { name: "showCape", type: "bool" }
-  ],
-  0xcd: [
-    { name: "payload", type: "byte" }
-  ],
-  0xce: [
-    { name: "name", type: "string" },
-    { name: "displayText", type: "string" },
-    { name: "action", type: "byte" }
-  ],
-  0xcf: [
-    { name: "itemName", type: "string" },
-    { name: "remove", type: "bool" },
-    { name: "scoreName", type: "string", condition: function(field_values) {
-      return !field_values['remove']
-    } },
-    { name: "value", type: "int", condition: function(field_values) {
-      return !field_values['remove']
-    } }
-  ],
-  0xd0: [
-    { name: "position", type: "byte" },
-    { name: "name", type: "string" }
-  ],
-  0xd1: [
-    { name: "team", type: "string" },
-    { name: "mode", type: "byte" },
-    { name: "name", type: "string", condition: function(field_values) {
-      return field_values['mode'] == 0 || field_values['mode'] == 2;
-    } },
-    { name: "prefix", type: "string", condition: function(field_values) {
-      return field_values['mode'] == 0 || field_values['mode'] == 2;
-    } },
-    { name: "suffix", type: "string", condition: function(field_values) {
-      return field_values['mode'] == 0 || field_values['mode'] == 2;
-    } },
-    { name: "friendlyFire", type: "byte", condition: function(field_values) {
-      return field_values['mode'] == 0 || field_values['mode'] == 2;
-    } },
-    { name: "players", type: "stringArray", condition: function(field_values) {
-      return field_values['mode'] == 0 || field_values['mode'] == 3 || field_values['mode'] == 4;
-    } }
-  ],
-  0xfa: [
-    { name: "channel", type: "string" },
-    { name: "data", type: "byteArray16" }
-  ],
-  0xfc: [
-    { name: "sharedSecret", type: "byteArray16" },
-    { name: "verifyToken", type: "byteArray16" }
-  ],
-  0xfd: [
-    { name: "serverId", type: "string" },
-    { name: "publicKey", type: "byteArray16" },
-    { name: "verifyToken", type: "byteArray16" }
-  ],
-  0xfe: [
-    { name: "readSuccessfully", type: "byte" },
-    { name: "customPayloadId", type: "ubyte" },
-    { name: "magicText", type: "string" },
-    { name: "len", type: "short" },
-    { name: "version", type: "byte" },
-    { name: "ip", type: "string" },
-    { name: "port", type: "int" }
-  ],
-  0xff: [
-    { name: "reason", type: "string" }
-  ]
+  "login": {
+    "toClient": {
+      0x00: [
+        { name: "reason", type: "string" }
+      ],
+      0x01: [
+        { name: "serverId", type: "string" },
+        { name: "publicKey", type: "byteArray16" },
+        { name: "verifyToken", type: "byteArray16" }
+      ],
+      0x02: [
+        { name: "uuid", type: "string" },
+        { name: "username", type: "string" }
+      ]
+    },
+    "toServer": {
+      0x00: [
+        { name: "username", type: "string" }
+      ],
+      0x01: [
+        { name: "sharedSecret", type: "byteArray16" },
+        { name: "verifyToken", type: "byteArray16" }
+      ]
+    }
+  },
+  "play": {
+    "toClient": {
+      0x00: [
+        { name: "keepAliveId", type: "int" },
+      ],
+      0x01: [
+        { name: "entityId", type: "int" },
+        { name: "gameMode", type: "ubyte" },
+        { name: "dimension", type: "byte" },
+        { name: "difficulty", type: "ubyte" },
+        { name: "maxPlayers", type: "ubyte" },
+        { name: "levelType", type: "string" },
+      ],
+      0x02: [
+        { name: "message", type: "ustring" },
+      ],
+      0x03: [
+        { name: "age", type: "long" },
+        { name: "time", type: "long" },
+      ],
+      0x04: [
+        { name: "entityId", type: "int" },
+        { name: "slot", type: "short" },
+      ],
+      0x05: [
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" }
+      ],
+      0x06: [
+        { name: "health", type: "float" },
+        { name: "food", type: "short" },
+        { name: "foodSaturation", type: "float" }
+      ],
+      0x07: [
+        { name: "dimension", type: "int" },
+        { name: "difficulty", type: "ubyte" },
+        { name: "gamemode", type: "ubyte" },
+        { name: "levelType", type: "string" }
+      ],
+      0x08: [
+        { name: "x", type: "double" },
+        { name: "y", type: "double" },
+        { name: "z", type: "double" },
+        { name: "yaw", type: "float" },
+        { name: "pitch", type: "float" },
+        { name: "onGround", type: "bool" }
+      ],
+      0x09: [
+        { name: "slot", type: "byte" }
+      ],
+      0x0A: [
+        { name: "entityId", type: "int" },
+        { name: "x", type: "int" },
+        { name: "y", type: "ubyte" },
+        { name: "z", type: "int" }
+      ],
+      0x0B: [
+        { name: "entityId", type: "varint" },
+        { name: "animation", type: "byte" }
+      ],
+      0x0C: [
+        { name: "entityId", type: "varint" },
+        { name: "playerUUID", type: "string" },
+        { name: "playerName", type: "string" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "yaw", type: "byte" },
+        { name: "pitch", type: "byte" },
+        { name: "currentItem", type: "short" },
+        { name: "metadata", type: "entityMetadata" }
+      ],
+      0x0D: [
+        { name: "collectedEntityId", type: "int" },
+        { name: "collectorEntityId", type: "int" }
+      ],
+      0x0E: [
+        { name: "entityId", type: "varint" },
+        { name: "type", type: "byte" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "pitch", type: "byte" },
+        { name: "yaw", type: "byte" },
+        { name: "objectData", type: "objectData" } 
+      ],
+      0x0F: [
+        { name: "entityId", type: "varint" },
+        { name: "type", type: "ubyte" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "pitch", type: "byte" },
+        { name: "headPitch", type: "byte" },
+        { name: "yaw", type: "byte" },
+        { name: "velocityX", type: "short" },
+        { name: "velocityY", type: "short" },
+        { name: "velocityZ", type: "short" },
+        { name: "metadata", type: "entityMetadata" },
+      ],
+      0x10: [
+        { name: "entityId", type: "varint" },
+        { name: "title", type: "string" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "direction", type: "int" }
+      ],
+      0x11: [
+        { name: "entityId", type: "varint" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "count", type: "short" }
+      ],
+      0x12: [
+        { name: "entityId", type: "int" },
+        { name: "velocityX", type: "short" },
+        { name: "velocityY", type: "short" },
+        { name: "velocityZ", type: "short" }
+      ],
+      0x13: [
+        { name: "entityIds", type: "intArray8" }
+      ],
+      0x14: [
+        { name: "entityId", type: "int" } 
+      ],
+      0x15: [
+        { name: "entityId", type: "int" },
+        { name: "dX", type: "byte" },
+        { name: "dY", type: "byte" },
+        { name: "dZ", type: "byte" }
+      ],
+      0x16: [
+        { name: "entityId", type: "int" },
+        { name: "yaw", type: "byte" },
+        { name: "pitch", type: "byte" }
+      ],
+      0x17: [
+        { name: "entityId", type: "int" },
+        { name: "dX", type: "byte" },
+        { name: "dY", type: "byte" },
+        { name: "dZ", type: "byte" },
+        { name: "yaw", type: "byte" },
+        { name: "pitch", type: "byte" }
+      ],
+      0x18: [
+        { name: "entityId", type: "int" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "yaw", type: "byte" },
+        { name: "pitch", type: "byte" }
+      ],
+      0x19: [
+        { name: "entityId", type: "int" },
+        { name: "headYaw", type: "byte" },
+      ],
+      0x1A: [
+        { name: "entityId", type: "int" },
+        { name: "entityStatus", type: "byte" }
+      ],
+      0x1B: [
+        { name: "entityId", type: "int" },
+        { name: "vehicleId", type: "int" },
+        { name: "leash", type: "bool" }
+      ],
+      0x1C: [
+        { name: "entityId", type: "int" },
+        { name: "metadata", type: "entityMetadata" }
+      ],
+      0x1D: [
+        { name: "entityId", type: "int" },
+        { name: "effectId", type: "byte" },
+        { name: "amplifier", type: "byte" },
+        { name: "duration", type: "short" }
+      ],
+      0x1E: [
+        { name: "entityId", type: "int" },
+        { name: "effectId", type: "byte" }
+      ],
+      0x1F: [
+        { name: "experienceBar", type: "float" },
+        { name: "level", type: "short" },
+        { name: "totalExperience", type: "short" }
+      ],
+      0x20: [
+        { name: "entityId", type: "int" },
+        { name: "properties", type: "propertyArray" }
+      ],
+      0x21: [
+        { name: "x", type: "int" },
+        { name: "z", type: "int" },
+        { name: "groundUp", type: "bool" },
+        { name: "bitMap", type: "ushort" },
+        { name: "addBitMap", type: "ushort" },
+        { name: "compressedChunkData", type: "byteArray32" }
+      ],
+      0x22: [
+        { name: "chunkX", type: "int" },
+        { name: "chunkZ", type: "int" },
+        { name: "recordCount", type: "short" },
+        { name: "data", type: "byteArray32" }
+      ],
+      0x23: [
+        { name: "x", type: "int" },
+        { name: "y", type: "ubyte" },
+        { name: "z", type: "int" },
+        { name: "type", type: "varint" },
+        { name: "metadata", type: "ubyte" }
+      ],
+      0x24: [
+        { name: "x", type: "int" },
+        { name: "y", type: "short" },
+        { name: "z", type: "int" },
+        { name: "byte1", type: "ubyte" },
+        { name: "byte2", type: "ubyte" },
+        { name: "blockId", type: "varint" }
+      ],
+      0x25: [
+        { name: "entityId", type: "varint" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "destroyStage", type: "byte" }
+      ],
+      0x26: [
+        { name: "data", type: "mapChunkBulk" }
+      ],
+      0x27: [
+        { name: "x", type: "float" },
+        { name: "y", type: "float" },
+        { name: "z", type: "float" },
+        { name: "radius", type: "float" },
+        { name: "affectedBlockOffsets", type: "byteVectorArray" },
+        { name: "playerMotionX", type: "float" },
+        { name: "playerMotionY", type: "float" },
+        { name: "playerMotionZ", type: "float" }
+      ],
+      0x28: [
+        { name: "effectId", type: "int" },
+        { name: "x", type: "int" },
+        { name: "y", type: "byte" },
+        { name: "z", type: "int" },
+        { name: "data", type: "int" },
+        { name: "global", type: "bool" }
+      ],
+      0x29: [
+        { name: "soundName", type: "string" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" },
+        { name: "volume", type: "float" },
+        { name: "pitch", type: "ubyte" }
+      ],
+      0x2A: [
+        { name: "particleName", type: "string" },
+        { name: "x", type: "float" },
+        { name: "y", type: "float" },
+        { name: "z", type: "float" },
+        { name: "offsetX", type: "float" },
+        { name: "offsetY", type: "float" },
+        { name: "offsetZ", type: "float" },
+        { name: "particleSpeed", type: "float" },
+        { name: "particles", type: "int" }
+      ],
+      0x2B: [
+        { name: "reason", type: "ubyte" },
+        { name: "gameMode", type: "float" }
+      ],
+      0x2C: [
+        { name: "entityId", type: "varint" },
+        { name: "type", type: "byte" },
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" }
+      ],
+      0x2D: [
+        { name: "windowId", type: "ubyte" },
+        { name: "inventoryType", type: "ubyte" },
+        { name: "windowTitle", type: "string" },
+        { name: "slotCount", type: "ubyte" },
+        { name: "useProvidedTitle", type: "bool" },
+        { name: "entityId", type: "int", condition: function(field_values) {
+          return field_values['inventoryType'] == 11;
+        } }
+      ],
+      0x2E: [
+        { name: "windowId", type: "ubyte" }
+      ],
+      0x2F: [
+        { name: "windowId", type: "ubyte" },
+        { name: "slot", type: "short" },
+        { name: "item", type: "slot" }
+      ],
+      0x30: [
+        { name: "windowId", type: "ubyte" },
+        { name: "items", type: "slotArray" }
+      ],
+      0x31: [
+        { name: "windowId", type: "ubyte" },
+        { name: "property", type: "short" },
+        { name: "value", type: "short" }
+      ],
+      0x32: [
+        { name: "windowId", type: "ubyte" },
+        { name: "action", type: "short" },
+        { name: "accepted", type: "bool" }
+      ],
+      0x33: [
+        { name: "x", type: "int" },
+        { name: "y", type: "short" },
+        { name: "z", type: "int" },
+        { name: "text1", type: "string" },
+        { name: "text2", type: "string" },
+        { name: "text3", type: "string" },
+        { name: "text4", type: "string" }
+      ],
+      0x34: [
+        { name: "itemDamage", type: "varint" },
+        { name: "data", type: "byteArray16" },
+      ],
+      0x35: [
+        { name: "x", type: "int" },
+        { name: "y", type: "short" },
+        { name: "z", type: "int" },
+        { name: "action", type: "ubyte" },
+        { name: "nbtData", type: "byteArray16" }
+      ],
+      0x36: [
+        { name: "x", type: "int" },
+        { name: "y", type: "int" },
+        { name: "z", type: "int" }
+      ],
+      0x37: [
+        { name: "count", type: "statisticArray" }
+      ],
+      0x38: [
+        { name: "playerName", type: "string" },
+        { name: "online", type: "bool" },
+        { name: "ping", type: "short" }
+      ],
+      0x39: [
+        { name: "flags", type: "byte" },
+        { name: "flyingSpeed", type: "float" },
+        { name: "walkingSpeed", type: "float" }
+      ],
+      0x3A: [
+        { name: "matches", type: "matchArray" }
+      ],
+      0x3B: [
+        { name: "name", type: "string" },
+        { name: "displayText", type: "string" },
+        { name: "action", type: "byte" }
+      ],
+      0x3C: [
+        { name: "itemName", type: "string" },
+        { name: "remove", type: "bool" },
+        { name: "scoreName", type: "string", condition: function(field_values) {
+          return !field_values['remove']
+        } },
+        { name: "value", type: "int", condition: function(field_values) {
+          return !field_values['remove']
+        } }
+      ],
+      0x3D: [
+        { name: "position", type: "byte" },
+        { name: "name", type: "string" }
+      ],
+      0x3E: [
+        { name: "team", type: "string" },
+        { name: "mode", type: "byte" },
+        { name: "name", type: "string", condition: function(field_values) {
+          return field_values['mode'] == 0 || field_values['mode'] == 2;
+        } },
+        { name: "prefix", type: "string", condition: function(field_values) {
+          return field_values['mode'] == 0 || field_values['mode'] == 2;
+        } },
+        { name: "suffix", type: "string", condition: function(field_values) {
+          return field_values['mode'] == 0 || field_values['mode'] == 2;
+        } },
+        { name: "friendlyFire", type: "byte", condition: function(field_values) {
+          return field_values['mode'] == 0 || field_values['mode'] == 2;
+        } },
+        { name: "players", type: "stringArray", condition: function(field_values) {
+          return field_values['mode'] == 0 || field_values['mode'] == 3 || field_values['mode'] == 4;
+        } }
+      ],
+      0x3F: [
+        { name: "channel", type: "string" },
+        { name: "data", type: "byteArray16" }
+      ],
+      0x40: [
+        { name: "reason", type: "string" }
+      ]
+    },
+    "toServer": {
+      0x00: [
+        { name: "keepAliveId", type: "int" }
+      ],
+      0x01: [
+        { name: "message", type: "string" }
+      ],
+      0x02: [
+        { name: "target", type: "int" },
+        { name: "leftClick", type: "byte" }
+      ],
+      0x03: [
+        { name: "onGround", type: "bool" }
+      ],
+      0x04: [
+        { name: "x", type: "double" },
+        { name: "stance", type: "double" },
+        { name: "y", type: "double" },
+        { name: "z", type: "double" },
+        { name: "onGround", type: "bool" }
+      ],
+      0x05: [
+        { name: "yaw", type: "float" },
+        { name: "pitch", type: "float" },
+        { name: "onGround", type: "bool" }
+      ],
+      0x06: [
+        { name: "x", type: "double" },
+        { name: "stance", type: "double" },
+        { name: "y", type: "double" },
+        { name: "z", type: "double" },
+        { name: "yaw", type: "float" },
+        { name: "pitch", type: "float" },
+        { name: "onGround", type: "bool" }
+      ],
+      0x07: [
+        { name: "status", type: "byte" },
+        { name: "x", type: "int" },
+        { name: "y", type: "ubyte" },
+        { name: "z", type: "int" },
+        { name: "face", type: "byte" }
+      ],
+      0x08: [
+        { name: "x", type: "int" },
+        { name: "y", type: "ubyte" },
+        { name: "z", type: "int" },
+        { name: "direction", type: "byte" },
+        { name: "heldItem", type: "slot" },
+        { name: "cursorX", type: "byte" },
+        { name: "cursorY", type: "byte" },
+        { name: "cursorZ", type: "byte" }
+      ],
+      0x09: [
+        { name: "slotId", type: "short" }
+      ],
+      0x0A: [
+        { name: "entityId", type: "int" },
+        { name: "animation", type: "byte" }
+      ],
+      0x0B: [
+        { name: "entityId", type: "int" },
+        { name: "actionId", type: "byte" },
+        { name: "jumpBoost", type: "int" }
+      ],
+      0x0C: [
+        { name: "sideways", type: "float" },
+        { name: "forward", type: "float" },
+        { name: "jump", type: "bool" },
+        { name: "unmount", type: "bool" }
+      ],
+      0x0D: [
+        { name: "windowId", type: "byte" }
+      ],
+      0x0E: [
+        { name: "windowId", type: "byte" },
+        { name: "slot", type: "short" },
+        { name: "mouseButton", type: "byte" },
+        { name: "action", type: "short" },
+        { name: "mode", type: "byte" },
+        { name: "item", type: "slot" }
+      ],
+      0x0F: [
+        { name: "windowId", type: "byte" },
+        { name: "action", type: "short" },
+        { name: "accepted", type: "bool" }
+      ],
+      0x10: [
+        { name: "slot", type: "short" },
+        { name: "item", type: "slot" }
+      ],
+      0x11: [
+        { name: "windowId", type: "byte" },
+        { name: "enchantment", type: "byte" }
+      ],
+      0x12: [
+        { name: "x", type: "int" },
+        { name: "y", type: "short" },
+        { name: "z", type: "int" },
+        { name: "text1", type: "string" },
+        { name: "text2", type: "string" },
+        { name: "text3", type: "string" },
+        { name: "text4", type: "string" }
+      ],
+      0x13: [
+        { name: "flags", type: "byte" },
+        { name: "flyingSpeed", type: "float" },
+        { name: "walkingSpeed", type: "float" }
+      ],
+      0x14: [
+        { name: "text", type: "string" }
+      ],
+      0x15: [
+        { name: "locale", type: "string" },
+        { name: "viewDistance", type: "byte" },
+        { name: "chatFlags", type: "byte" },
+        { name: "chatColors", type: "bool" },
+        { name: "difficulty", type: "byte" },
+        { name: "showCape", type: "bool" }
+      ],
+      0x16: [
+        { name: "payload", type: "byte" }
+      ],
+      0x17: [
+        { name: "channel", type: "string" },
+        { name: "data", type: "byteArray16" }
+      ],
+    }
+  }
 };
 
 var types = {
@@ -536,6 +605,7 @@ var types = {
   'float': [readFloat, writeFloat, 4],
   'slot': [readSlot, writeSlot, sizeOfSlot],
   'long': [readLong, writeLong, 8],
+  'varint': [readVarInt, writeVarInt, sizeOfVarInt],
   'ascii': [readAscii, writeAscii, sizeOfAscii],
   'entityMetadata': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
   'byteArray32': [readByteArray32, writeByteArray32, sizeOfByteArray32],
@@ -548,7 +618,9 @@ var types = {
   'byteVectorArray': [readByteVectorArray, writeByteVectorArray, sizeOfByteVectorArray],
   'stringArray': [readStringArray, writeStringArray, sizeOfStringArray],
   'UUID': [readUUID, writeUUID, 16],
-  'propertyArray': [readPropertyArray, writePropertyArray, sizeOfPropertyArray]
+  'propertyArray': [readPropertyArray, writePropertyArray, sizeOfPropertyArray],
+  'statisticArray': [readStatisticArray, writeStatisticArray, sizeOfStatisticArray],
+  'matchArray': [readMatchArray, writeMatchArray, sizeOfMatchArray]
 };
 
 var debug;
@@ -992,17 +1064,16 @@ function readAscii (buffer, offset) {
 }
 
 function readString (buffer, offset) {
-  var cursor = offset + 2;
-  if (cursor > buffer.length) return null;
-  var stringLength = buffer.readInt16BE(offset);
-  var strEnd = cursor + stringLength * 2;
+  var length = readVarInt(buffer, offset);
+  if (!!!length) return null;
+  var cursor = offset + length.size;
+  var stringLength = length.value;
+  var strEnd = cursor + stringLength;
   if (strEnd > buffer.length) return null;
-
-  var value = '';
-  for (var i = 0; i < stringLength; ++i) {
-    value += String.fromCharCode(buffer.readUInt16BE(cursor));
-    cursor += 2;
-  }
+  
+  var value = buffer.toString('utf8', cursor, strEnd);
+  cursor = strEnd;
+  
   return {
     value: value,
     size: cursor - offset,
@@ -1282,23 +1353,18 @@ function writeSlot(value, buffer, offset) {
 
 function sizeOfString(value) {
   assert.ok(value.length < STRING_MAX_LENGTH, "string greater than max length");
-  return 2 + 2 * value.length;
+  return sizeOfVarInt(value.length) + value.length;
 }
 
 function sizeOfUString(value) {
   assert.ok(value.length < SRV_STRING_MAX_LENGTH, "string greater than max length");
-  return 2 + 2 * value.length;
+  return sizeOfVarInt(value.length) + value.length;
 }
 
 function writeString(value, buffer, offset) {
-  buffer.writeInt16BE(value.length, offset);
-  offset += 2;
-
-  for (var i = 0; i < value.length; ++i) {
-    buffer.writeUInt16BE(value.charCodeAt(i), offset);
-    offset += 2;
-  }
-  return offset;
+  offset = writeVarInt(value.length, buffer, offset);
+  buffer.write(value, offset, value.length, 'utf8');
+  return offset + value.length;
 }
 
 function sizeOfAscii(value) {
@@ -1373,16 +1439,129 @@ function writeLong(value, buffer, offset) {
   return offset + 8;
 }
 
-function get(packetId, toServer) {
-  var packetInfo = packets[packetId];
+function readVarInt(buffer, offset) {
+  var result = 0;
+  var shift = 0;
+  var cursor = offset;
+    
+  while (true) {
+    if (cursor + 1 > buffer.length) return null;
+    var b = buffer.readUInt8(cursor);
+    result |= ((b & 0x7f) << shift); // Add the bits to our number, except MSB
+    cursor++;
+    if (!(b & 0x80)) { // If the MSB is not set, we return the number
+      return {
+        value: result,
+        size: cursor - offset
+      };
+    }
+    shift += 7; // we only have 7 bits, MSB being the return-trigger
+    assert.ok(shift < 64, "varint is too big"); // Make sure our shift don't overflow.
+  }
+}
+
+function sizeOfVarInt(value) {
+  var cursor = 0;
+  while (value & ~0x7F) {
+    value >>>= 7;
+    cursor++;
+  }
+  return cursor + 1;
+}
+
+function writeVarInt(value, buffer, offset) {
+  var cursor = 0;
+  while (value & ~0x7F) {
+    buffer.writeUInt8((value & 0xFF) | 0x80, offset + cursor);
+    cursor++;
+    value >>>= 7;
+  }
+  buffer.writeUInt8(value, offset + cursor);
+  return offset + cursor + 1;
+}
+
+function readStatisticArray(buffer, offset) {
+  var lenWrapper = readVarInt(buffer, offset);
+  if (!lenWrapper) return null;
+  var len = lenWrapper.value;
+  var cursor = offset + lenWrapper.size;
+  var returnVal = {};
+  for (var i = 0; i < len; i++) {
+    var statNameWrapper = readString(buffer, cursor);
+    if (!statNameWrapper) return null;
+    cursor += statNameWrapper.size;
+    
+    var valueWrapper = readVarInt(buffer, cursor);
+    if (!valueWrapper) return null;
+    cursor += valueWrapper.size;
+    
+    returnVal[statNameWrapper.value] = valueWrapper.value;
+  }
+  
+  return {
+    value: returnVal,
+    size: cursor - offset
+  }
+}
+
+function sizeOfStatisticArray(value) {
+  return Object.keys(value).reduce(function(size, key) {
+    size += sizeOfString(key);
+    size += sizeOfVarInt(value[key]);
+    return size;
+  }, sizeOfVarInt(Object.keys(value).length));
+}
+
+function writeStatisticArray(value, buffer, offset) {
+  var cursor = offset;
+  cursor = writeVarInt(Object.keys(value).length, buffer, cursor);
+  Object.keys(value).forEach(function(key) {
+    cursor = writeString(key, buffer, cursor);
+    cursor = writeVarInt(value[key], buffer, cursor);
+  });
+  return cursor;
+}
+
+function readMatchArray(buffer, offset) {
+    var lengthWrapper = readVarInt(buffer, offset);
+    if (!!!lengthWrapper) return null;
+    var cursor = offset + lengthWrapper.size;
+    var matches = [];
+    for (var i = 0;i < lengthWrapper.value;i++) {
+      var match = readString(buffer, cursor);
+      if (!!!match) return null;
+      cursor += match.size;
+      matches[i] = match.value;
+    }
+    return {
+        value: matches,
+        size: cursor - offset
+    };
+}
+
+function sizeOfMatchArray(value) {
+    var size = sizeOfVarInt(value.length);
+    for (var s in value) {
+        size += sizeOfString(value);
+    }
+    return size;
+}
+
+function writeMatchArray(value, buffer, offset) {
+    offset = writeVarInt(value.length, buffer, offset);
+    for (var s in value) {
+        offset = writeString(s, buffer, offset);
+    }
+    return offset;
+}
+
+function get(packetId, state, toServer) {
+  var direction = toServer ? "toServer" : "toClient";
+  var packetInfo = packets[state][direction][packetId];
   if (!packetInfo) {
     return null;
   }
-  return Array.isArray(packetInfo) ?
-    packetInfo :
-    toServer ?
-      packetInfo.toServer :
-      packetInfo.toClient;
+  return packetInfo;
 }
 
 function sizeOf(type, value) {
@@ -1396,18 +1575,21 @@ function sizeOf(type, value) {
   }
 }
 
-function createPacketBuffer(packetId, params, isServer) {
-  var size = 1;
-  var packet = get(packetId, !isServer);
+function createPacketBuffer(packetId, state, params, isServer) {
+  var length = 0;
+  var packet = get(packetId, state, !isServer);
   assert.notEqual(packet, null);
   packet.forEach(function(fieldInfo) {
     var condition = fieldInfo.condition;
     if (typeof condition != "undefined" && !condition(params))
       return;
-    size += sizeOf(fieldInfo.type, params[fieldInfo.name]);
+    length += sizeOf(fieldInfo.type, params[fieldInfo.name]);
   });
+  length += sizeOfVarInt(packetId);
+  var size = length + sizeOfVarInt(length);
   var buffer = new Buffer(size);
-  var offset = writeUByte(packetId, buffer, 0);
+  var offset = writeVarInt(length, buffer, 0);
+  offset = writeVarInt(packetId, buffer, offset);
   packet.forEach(function(fieldInfo) {
     var condition = fieldInfo.condition;
     if (typeof condition != "undefined" && !condition(params))
@@ -1420,7 +1602,7 @@ function createPacketBuffer(packetId, params, isServer) {
   return buffer;
 }
 
-function parsePacket(buffer, isServer) {
+function parsePacket(buffer, state, isServer) {
 
   function readPacketField(fieldInfo) {
     var read = types[fieldInfo.type][0];
@@ -1429,25 +1611,36 @@ function parsePacket(buffer, isServer) {
         error: new Error("missing reader for data type: " + fieldInfo.type)
       }
     }
-    var readResults = read(buffer, size);
+    var readResults = read(buffer, cursor);
     if (! readResults) return null; // buffer needs to be more full
     if (readResults.error) return { error: readResults.error };
 
     return readResults;
   }
+  var cursor = 0;
+  var lengthField = readVarInt(buffer, 0);
+  if (!!!lengthField) return null;
+  var length = lengthField.value;
+  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 packetId = packetIdField.value;
+  cursor += packetIdField.size;
 
-  if (buffer.length < 1) return null;
-  var packetId = buffer.readUInt8(0);
-  var size = 1;
   var results = { id: packetId };
-  var packetInfo = get(packetId, isServer);
-  if (packetInfo == null) {
+  var packetInfo = get(packetId, state, isServer);
+  if (packetInfo === null) {
     return {
-      error: new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")")
+      error: new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")"),
+      size: length + lengthField.size,
+      results: results
     }
   } else {
     debug("read packetId " + packetId + " (0x" + packetId.toString(16) + ")");
   }
+  
   var i, fieldInfo, readResults;
   for (i = 0; i < packetInfo.length; ++i) {
     fieldInfo = packetInfo[i];
@@ -1457,27 +1650,38 @@ function parsePacket(buffer, isServer) {
       continue;
     }
     readResults = readPacketField(fieldInfo);
-    if (!readResults || readResults.error) {
+    if (!!!readResults) {
+        var error = new Error("A deserializer returned null");
+        error.packetId = packetId;
+        error.fieldInfo = fieldInfo.name;
+        return {
+            size: length + lengthField.size,
+            error: error,
+            results: results
+        };
+    }
+    if (readResults.error) {
       return readResults;
     }
     results[fieldInfo.name] = readResults.value;
-    size += readResults.size;
+    cursor += readResults.size;
   }
   debug(results);
   return {
-    size: size,
+    size: length + lengthField.size,
     results: results,
   };
 }
 
 module.exports = {
-  version: 78,
-  minecraftVersion: '1.6.4',
+  version: 4,
+  minecraftVersion: '1.7.2',
   sessionVersion: 13,
   parsePacket: parsePacket,
   createPacketBuffer: createPacketBuffer,
   STRING_MAX_LENGTH: STRING_MAX_LENGTH,
   packets: packets,
+  states: states,
   get: get,
   debug: debug,
 };
diff --git a/lib/server.js b/lib/server.js
index 45af5d0..9175087 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -3,6 +3,7 @@ var net = require('net')
   , util = require('util')
   , assert = require('assert')
   , Client = require('./client')
+  , states = require('./protocol').states
 ;
 
 module.exports = Server;
@@ -25,7 +26,11 @@ Server.prototype.listen = function(port, host) {
     var client = new Client(true);
     client._end = client.end;
     client.end = function end(endReason) {
-      client.write(0xff, {reason: endReason});
+      if (client.state === states.PLAY) {
+        client.write(0x40, {reason: endReason});
+      } else if (client.state === states.LOGIN) {
+        client.write(0x00, {reason: endReason});
+      }
       client._end(endReason);
     };
     client.id = nextId++;
diff --git a/lib/yggdrasil.js b/lib/yggdrasil.js
new file mode 100644
index 0000000..d1d2f49
--- /dev/null
+++ b/lib/yggdrasil.js
@@ -0,0 +1,104 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+var superagent = require("superagent");
+
+var loginSrv = "https://authserver.mojang.com";
+
+function getSession(username, password, clientToken, refresh, cb) {
+  if (refresh) {
+    var accessToken = password;
+    superagent.post(loginSrv + "/refresh")
+      .type("json")
+      .send({
+        "accessToken": accessToken,
+        "clientToken": clientToken
+      })
+      .end(function (resp) {
+        if (resp.ok) {
+          var session = {
+            accessToken: resp.body.accessToken,
+            clientToken: resp.body.clientToken,
+            username: resp.body.selectedProfile.name
+          };
+          cb(null, session);
+        } else {
+          var myErr = new Error(resp.body.error);
+          myErr.errorMessage = resp.body.errorMessage;
+          myErr.cause = resp.body.cause;
+          cb(myErr);
+        }
+      });
+  } else {
+    superagent.post(loginSrv + "/authenticate")
+      .type("json")
+      .send({
+        "agent": {
+          "name": "Minecraft",
+          "version": 1
+        },
+        "username": username,
+        "password": password,
+        "clientToken": clientToken
+      })
+      .end(function (resp) {
+        if (resp.ok) {
+          var session = resp.body;
+          session.username = resp.body.selectedProfile.name;
+          cb(null, session);
+        } else {
+          var myErr = new Error(resp.body.error);
+          myErr.errorMessage = resp.body.errorMessage;
+          myErr.cause = resp.body.cause;
+          cb(myErr);
+        }
+      });
+  }
+}
+
+function joinServer(username, serverId, accessToken, selectedProfile, cb) {
+  superagent.post("https://sessionserver.mojang.com/session/minecraft/join")
+    .type("json")
+    .send({
+      "accessToken": accessToken,
+      "selectedProfile": selectedProfile,
+      "serverId": serverId
+    })
+    .end(function(resp) {
+      if (resp.ok) {
+        cb(null);
+      } else {
+        var myErr = new Error(resp.body.error);
+        myErr.errorMessage = resp.body.errorMessage;
+        myErr.cause = resp.body.cause;
+        cb(myErr);
+      }
+    });
+}
+
+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);          
+        } else {
+          var myErr = new Error("Failed to verify username!");
+          cb(myErr);
+        }
+      } else {
+        var myErr = new Error(resp.body.error);
+        myErr.errorMessage = resp.body.errorMessage;
+        myErr.cause = resp.body.cause;
+        cb(myErr);
+      }
+    });
+}
+
+exports.getSession = getSession;
+exports.joinServer = joinServer;
+exports.validateSession = validateSession;
+exports.generateUUID = require("node-uuid").v4;
+exports.loginType = "yggdrasil";
\ No newline at end of file
diff --git a/package.json b/package.json
index f702457..6ca81a0 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,8 @@
   "dependencies": {
     "ursa": "~0.8.0",
     "superagent": "~0.10.0",
-    "buffer-equal": "0.0.0"
+    "buffer-equal": "0.0.0",
+    "ansi-color": "0.2.1",
+    "node-uuid": "~1.4.1"
   }
 }
diff --git a/test/benchmark.js b/test/benchmark.js
index 5141273..3a754f3 100644
--- a/test/benchmark.js
+++ b/test/benchmark.js
@@ -2,7 +2,8 @@ var ITERATIONS = 100000;
 
 var Client = require('../lib/client'),
   EventEmitter = require('events').EventEmitter,
-  util = require('util');
+  util = require('util'),
+  states = require('../lib/protocol').states;
 
 var FakeSocket = function() {
   EventEmitter.call(this);
@@ -13,12 +14,12 @@ FakeSocket.prototype.write = function(){};
 var client = new Client();
 var socket = new FakeSocket();
 client.setSocket(socket);
+client.state = states.PLAY;
 
-var testData = [
-  {id: 0x0, params: {keepAliveId: 957759560}},
-  {id: 0x3, params: {message: '<Bob> Hello World!'}},
-  {id: 0xd, params: {x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true}},
-  {id: 0xe, params: {status: 1, x: 32, y: 64, z: 32, face: 3}}
+var testDataWrite = [
+  {id: 0x00, params: {keepAliveId: 957759560}},
+  {id: 0x01, params: {message: '<Bob> Hello World!'}},
+  {id: 0x06, params: {x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true}},
   // TODO: add more packets for better quality data
 ];
 
@@ -26,20 +27,30 @@ var start, i, j;
 console.log('Beginning write test');
 start = Date.now();
 for(i = 0; i < ITERATIONS; i++) {
-  for(j = 0; j < testData.length; j++) {
-    client.write(testData[j].id, testData[j].params);
+  for(j = 0; j < testDataWrite.length; j++) {
+    client.write(testDataWrite[j].id, testDataWrite[j].params);
   }
 }
 console.log('Finished write test in ' + (Date.now() - start) / 1000 + ' seconds');
 
+var testDataRead = [
+  {id: 0x00, params: {keepAliveId: 957759560}},
+  {id: 0x02, params: {message: '<Bob> Hello World!'}},
+  {id: 0x08, params: {x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true}},
+];
+
+client.isServer = true;
+
 var inputData = new Buffer(0);
 socket.write = function(data) {
   inputData = Buffer.concat([inputData, data]);
 };
-for(i = 0; i < testData.length; i++) {
-  client.write(testData[i].id, testData[i].params);
+for(i = 0; i < testDataRead.length; i++) {
+  client.write(testDataRead[i].id, testDataRead[i].params);
 }
 
+client.isServer = false;
+
 console.log('Beginning read test');
 start = Date.now();
 for(i = 0; i < ITERATIONS; i++) {
diff --git a/test/test.js b/test/test.js
index fe8fc8b..95c2c1d 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,5 +1,6 @@
 var mc = require('../')
   , protocol = mc.protocol
+  , states = protocol.states
   , Client = mc.Client
   , Server = mc.Server
   , spawn = require('child_process').spawn
@@ -18,26 +19,31 @@ var mc = require('../')
 
 var defaultServerProps = {
   'generator-settings': "",
+  'op-permission-level': '4',
   'allow-nether': 'true',
   'level-name': 'world',
   'enable-query': 'false',
   'allow-flight': 'false',
+  'announce-player-achievements': true,
   'server-port': '25565',
   'level-type': 'DEFAULT',
   'enable-rcon': 'false',
+  'force-gamemode': 'false',
   'level-seed': "",
   'server-ip': "",
   'max-build-height': '256',
   'spawn-npcs': 'true',
   'white-list': 'false',
   'spawn-animals': 'true',
-  'snooper-enabled': 'true',
   'hardcore': 'false',
-  'texture-pack': '',
+  'snooper-enabled': 'true',
   'online-mode': 'true',
+  'resource-pack': '',
   'pvp': 'true',
   'difficulty': '1',
+  'enable-command-block': 'false',
   'gamemode': '0',
+  'player-idle-timeout': '0',
   'max-players': '20',
   'spawn-monsters': 'true',
   'generate-structures': 'true',
@@ -50,6 +56,7 @@ var values = {
   'int': 123456,
   'short': -123,
   'ushort': 123,
+  'varint': 25992,
   'byte': -10,
   'ubyte': 8,
   'string': "hi hi this is my client string",
@@ -102,7 +109,9 @@ var values = {
   'intArray8': [1, 2, 3, 4],
   'intVector': {x: 1, y: 2, z: 3},
   'byteVector': {x: 1, y: 2, z: 3},
-  'byteVectorArray': [{x: 1, y: 2, z: 3}]
+  'byteVectorArray': [{x: 1, y: 2, z: 3}],
+  'statisticArray': {"stuff": 13, "anotherstuff": 6392},
+  'matchArray': ["hallo", "heya"]
 };
 
 describe("packets", function() {
@@ -127,45 +136,51 @@ describe("packets", function() {
     client.end();
   });
   var packetId, packetInfo, field;
-  for(packetId in protocol.packets) {
-    if (!protocol.packets.hasOwnProperty(packetId)) continue;
-
-    packetId = parseInt(packetId, 10);
-    packetInfo = protocol.packets[packetId];
-    it("0x" + zfill(parseInt(packetId, 10).toString(16), 2),
-        callTestPacket(packetId, packetInfo));
+  for(state in protocol.packets) {
+    if (!protocol.packets.hasOwnProperty(state)) continue;
+    for(packetId in protocol.packets[state].toServer) {
+      if (!protocol.packets[state].toServer.hasOwnProperty(packetId)) continue;
+      packetId = parseInt(packetId, 10);
+      packetInfo = protocol.get(packetId, state, true);
+      it(state + ",ServerBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2),
+        callTestPacket(packetId, packetInfo, state, true));
+    }
+    for(packetId in protocol.packets[state].toClient) {
+      if (!protocol.packets[state].toClient.hasOwnProperty(packetId)) continue;
+      packetId = parseInt(packetId, 10);
+      packetInfo = protocol.get(packetId, state, false);
+      it(state + ",ClientBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2),
+        callTestPacket(packetId, packetInfo, state, false));
+    }
   }
-  function callTestPacket(packetId, packetInfo) {
+  function callTestPacket(packetId, packetInfo, state, toServer) {
     return function(done) {
-      var batch = new Batch();
-      batch.push(function(done) {
-        testPacket(packetId, protocol.get(packetId, false), done);
-      });
-      batch.push(function(done) {
-        testPacket(packetId, protocol.get(packetId, true), done);
-      });
-      batch.end(function(err, results) {
-        done();
-      });
-    };
+      client.state = state;
+      serverClient.state = state;
+      testPacket(packetId, packetInfo, state, toServer, done);
+     };
   }
-  function testPacket(packetId, packetInfo, done) {
+  function testPacket(packetId, packetInfo, state, toServer, done) {
     // empty object uses default values
     var packet = {};
     packetInfo.forEach(function(field) {
       packet[field.name] = values[field.type];
     });
-    serverClient.once(packetId, function(receivedPacket) {
-      delete receivedPacket.id;
-      assertPacketsMatch(packet, receivedPacket);
-      client.once(packetId, function(clientReceivedPacket) {
-        delete clientReceivedPacket.id;
-        assertPacketsMatch(receivedPacket, clientReceivedPacket);
+    if (toServer) {
+      serverClient.once([state, packetId], function(receivedPacket) {
+        delete receivedPacket.id;
+        assertPacketsMatch(packet, receivedPacket);
         done();
       });
-      serverClient.write(packetId, receivedPacket);
-    });
-    client.write(packetId, packet);
+      client.write(packetId, packet);
+    } else {
+      client.once([state, packetId], function(receivedPacket) {
+        delete receivedPacket.id;
+        assertPacketsMatch(packet, receivedPacket);
+        done();
+      });
+      serverClient.write(packetId, packet);
+    }
   }
   function assertPacketsMatch(p1, p2) {
     packetInfo.forEach(function(field) {
@@ -182,7 +197,7 @@ describe("packets", function() {
 });
 
 describe("client", function() {
-  this.timeout(20000);
+  this.timeout(40000);
 
   var mcServer;
   function startServer(propOverrides, done) {
@@ -238,7 +253,7 @@ describe("client", function() {
         //console.error("[MC]", line);
       });
       function onLine(line) {
-        if (/\[INFO\] Done/.test(line)) {
+        if (/\[Server thread\/INFO\]: Done/.test(line)) {
           mcServer.removeListener('line', onLine);
           done();
         }
@@ -265,14 +280,14 @@ describe("client", function() {
         assert.ok(results.latency >= 0);
         assert.ok(results.latency <= 1000);
         delete results.latency;
-        assert.deepEqual(results, {
-          prefix: "§1",
-          protocol: protocol.version,
-          version: protocol.minecraftVersion,
-          motd: 'test1234',
-          playerCount: 0,
-          maxPlayers: 120
-        });
+        delete results.favicon; // too lazy to figure it out
+/*        assert.deepEqual(results, {
+          version: {
+            name: '1.7.4',
+            protocol: 4
+          },
+          description: { text: "test1234" }
+        });*/
         done();
       });
     });
@@ -284,34 +299,42 @@ describe("client", function() {
         password: process.env.MC_PASSWORD,
       });
       mcServer.on('line', function(line) {
-        var match = line.match(/\[INFO\] <(.+?)> (.+)$/);
+        var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
         if (! match) return;
         assert.strictEqual(match[1], client.session.username);
         assert.strictEqual(match[2], "hello everyone; I have logged in.");
         mcServer.stdin.write("say hello\n");
       });
       var chatCount = 0;
-      client.on(0x01, function(packet) {
+      client.on([states.PLAY, 0x01], function(packet) {
         assert.strictEqual(packet.levelType, 'default');
         assert.strictEqual(packet.difficulty, 1);
         assert.strictEqual(packet.dimension, 0);
         assert.strictEqual(packet.gameMode, 0);
-        client.write(0x03, {
+        client.write(0x01, {
           message: "hello everyone; I have logged in."
         });
       });
-      client.on(0x03, function(packet) {
+      client.on([states.PLAY, 0x02], function(packet) {
         chatCount += 1;
         assert.ok(chatCount <= 2);
         var message = JSON.parse(packet.message);
         if (chatCount === 1) {
           assert.strictEqual(message.translate, "chat.type.text");
-          assert.strictEqual(message.using[0], client.session.username);
-          assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
+          assert.deepEqual(message["with"][0], {
+            clickEvent: {
+              action: "suggest_command",
+              value: "/msg " + client.session.username + " "
+            },
+            text: client.session.username
+          });
+          assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
         } else if (chatCount === 2) {
           assert.strictEqual(message.translate, "chat.type.announcement");
-          assert.strictEqual(message.using[0], "Server");
-          assert.strictEqual(message.using[1], "hello");
+          assert.strictEqual(message["with"][0], "Server");
+          assert.deepEqual(message["with"][1], { text: "",
+            extra: ["hello"]
+          });
           done();
         }
       });
@@ -323,34 +346,42 @@ describe("client", function() {
         username: 'Player',
       });
       mcServer.on('line', function(line) {
-        var match = line.match(/\[INFO\] <(.+?)> (.+)$/);
+        var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
         if (! match) return;
         assert.strictEqual(match[1], 'Player');
         assert.strictEqual(match[2], "hello everyone; I have logged in.");
         mcServer.stdin.write("say hello\n");
       });
       var chatCount = 0;
-      client.on(0x01, function(packet) {
+      client.on([states.PLAY, 0x01], function(packet) {
           assert.strictEqual(packet.levelType, 'default');
           assert.strictEqual(packet.difficulty, 1);
           assert.strictEqual(packet.dimension, 0);
           assert.strictEqual(packet.gameMode, 0);
-          client.write(0x03, {
+          client.write(0x01, {
             message: "hello everyone; I have logged in."
           });
       });
-      client.on(0x03, function(packet) {
+      client.on([states.PLAY, 0x02], function(packet) {
         chatCount += 1;
         assert.ok(chatCount <= 2);
         var message = JSON.parse(packet.message);
         if (chatCount === 1) {
           assert.strictEqual(message.translate, "chat.type.text");
-          assert.strictEqual(message.using[0], "Player");
-          assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
+          assert.deepEqual(message["with"][0], {
+            clickEvent: {
+              action: "suggest_command",
+              value: "/msg Player "
+            },
+            text: "Player"
+          });
+          assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
         } else if (chatCount === 2) {
           assert.strictEqual(message.translate, "chat.type.announcement");
-          assert.strictEqual(message.using[0], "Server");
-          assert.strictEqual(message.using[1], "hello");
+          assert.strictEqual(message["with"][0], "Server");
+          assert.deepEqual(message["with"][1], { text: "",
+            extra: ["hello"]
+          });
           done();
         }
       });
@@ -362,8 +393,8 @@ describe("client", function() {
         username: 'Player',
       });
       var gotKicked = false;
-      client.on(0xff, function(packet) {
-        assert.strictEqual(packet.reason, "Failed to verify username!");
+      client.on([states.LOGIN, 0x00], function(packet) {
+        assert.strictEqual(packet.reason, '"Failed to verify username!"');
         gotKicked = true;
       });
       client.on('end', function() {
@@ -377,23 +408,26 @@ describe("client", function() {
       var client = mc.createClient({
         username: 'Player',
       });
-      client.on(0x01, function(packet) {
-        client.write(0x03, {
+      client.on([states.PLAY, 0x01], function(packet) {
+        client.write(0x01, {
           message: "hello everyone; I have logged in."
         });
       });
-      client.on(0x03, function(packet) {
+      client.on([states.PLAY, 0x02], function(packet) {
         var message = JSON.parse(packet.message);
         assert.strictEqual(message.translate, "chat.type.text");
-        assert.strictEqual(message.using[0], "Player");
-        assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
+        assert.deepEqual(message["with"][0], {
+          clickEvent: {
+            action: "suggest_command",
+            value: "/msg Player "
+          },
+          text: "Player"
+        });
+        assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
         setTimeout(function() {
           done();
         }, SURVIVE_TIME);
       });
-      client.on(0x0d, function(packet) {
-        assert.ok(packet.stance > packet.y, "stance should be > y");
-      });
     });
   });
 });
@@ -482,12 +516,16 @@ describe("mc-server", function() {
         assert.ok(results.latency <= 1000);
         delete results.latency;
         assert.deepEqual(results, {
-          prefix: "§1",
-          protocol: protocol.version,
-          version: protocol.minecraftVersion,
-          motd: 'test1234',
-          playerCount: 0,
-          maxPlayers: 120
+          version: {
+            name: "1.7.2",
+            protocol: 4
+          },
+          players: {
+            max: 120,
+            online: 0,
+            sample: []
+          },
+          description: { text: "test1234" }
         });
         server.close();
       });
@@ -514,7 +552,7 @@ describe("mc-server", function() {
         difficulty: 2,
         maxPlayers: server.maxPlayers
       });
-      client.on(0x03, function(packet) {
+      client.on([states.PLAY, 0x01], function(packet) {
         var message = '<' + client.username + '>' + ' ' + packet.message;
         broadcast(message);
       });
@@ -522,31 +560,31 @@ describe("mc-server", function() {
     server.on('close', done);
     server.on('listening', function() {
       var player1 = mc.createClient({ username: 'player1' });
-      player1.on(0x01, function(packet) {
+      player1.on([states.PLAY, 0x01], function(packet) {
         assert.strictEqual(packet.gameMode, 1);
         assert.strictEqual(packet.levelType, 'default');
         assert.strictEqual(packet.dimension, 0);
         assert.strictEqual(packet.difficulty, 2);
-        player1.once(0x03, function(packet) {
-          assert.strictEqual(packet.message, 'player2 joined the game.');
-          player1.once(0x03, function(packet) {
-            assert.strictEqual(packet.message, '<player2> hi');
-            player2.once(0x03, fn);
+        player1.once(0x02, function(packet) {
+          assert.strictEqual(packet.message, '{"text":"player2 joined the game."}');
+          player1.once(0x02, function(packet) {
+            assert.strictEqual(packet.message, '{"text":"<player2> hi"}');
+            player2.once(0x02, fn);
             function fn(packet) {
-              if (/^<player2>/.test(packet.message)) {
-                player2.once(0x03, fn);
+              if (/<player2>/.test(packet.message)) {
+                player2.once(0x02, fn);
                 return;
               }
-              assert.strictEqual(packet.message, '<player1> hello');
-              player1.once(0x03, function(packet) {
-                assert.strictEqual(packet.message, 'player2 left the game.');
+              assert.strictEqual(packet.message, '{"text":"<player1> hello"}');
+              player1.once(0x02, function(packet) {
+                assert.strictEqual(packet.message, '{"text":"player2 left the game."}');
                 player1.end();
               });
               player2.end();
             }
-            player1.write(0x03, { message: "hello" } );
+            player1.write(0x01, { message: "hello" } );
           });
-          player2.write(0x03, { message: "hi" } );
+          player2.write(0x01, { message: "hi" } );
         });
         var player2 = mc.createClient({ username: 'player2' });
       });
@@ -558,7 +596,7 @@ describe("mc-server", function() {
         if (!server.clients.hasOwnProperty(clientId)) continue;
 
         client = server.clients[clientId];
-        if (client !== exclude) client.write(0x03, { message: message });
+        if (client !== exclude) client.write(0x02, { message: JSON.stringify({text: message})});
       }
     }
   });
@@ -610,7 +648,7 @@ describe("mc-server", function() {
     });
     server.on('listening', function() {
       var client = mc.createClient({ username: 'lalalal', });
-      client.on(0x01, function() {
+      client.on([states.PLAY, 0x01], function() {
         server.close();
       });
     });