2013-01-03 23:14:19 -05:00
|
|
|
var Iconv = require('iconv').Iconv
|
|
|
|
, assert = require('assert')
|
|
|
|
, toUcs2 = new Iconv('UTF-8', 'utf16be')
|
|
|
|
, fromUcs2 = new Iconv('utf16be', 'UTF-8')
|
|
|
|
|
|
|
|
var STRING_MAX_LENGTH = 240;
|
|
|
|
|
|
|
|
exports.version = 51;
|
2013-01-04 01:45:57 -05:00
|
|
|
exports.minecraftVersion = '1.4.6';
|
2013-01-03 23:14:19 -05:00
|
|
|
exports.sessionVersion = 13;
|
|
|
|
exports.parsePacket = parsePacket;
|
|
|
|
exports.createPacketBuffer = createPacketBuffer;
|
|
|
|
|
|
|
|
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: [
|
|
|
|
{ name: "message", type: "string" }
|
|
|
|
],
|
|
|
|
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: "short" },
|
|
|
|
{ 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" }
|
|
|
|
],
|
|
|
|
},
|
|
|
|
0x0e: [
|
|
|
|
{ name: "status", type: "byte" },
|
|
|
|
{ name: "x", type: "int" },
|
|
|
|
{ name: "y", type: "byte" },
|
|
|
|
{ name: "z", type: "int" },
|
|
|
|
{ name: "face", type: "byte" }
|
|
|
|
],
|
|
|
|
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: "byte" },
|
|
|
|
{ name: "z", type: "int" }
|
|
|
|
],
|
|
|
|
0x12: [
|
|
|
|
{ name: "entityId", type: "int" },
|
|
|
|
{ name: "animation", type: "byte" }
|
|
|
|
],
|
|
|
|
0x13: [
|
|
|
|
{ name: "entityId", type: "int" },
|
|
|
|
{ name: "actionId", type: "byte" }
|
|
|
|
],
|
|
|
|
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" }
|
|
|
|
],
|
|
|
|
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" }
|
|
|
|
],
|
|
|
|
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" }
|
|
|
|
],
|
|
|
|
0x33: [
|
|
|
|
{ name: "x", type: "int" },
|
|
|
|
{ name: "z", type: "int" },
|
|
|
|
{ name: "groundUp", type: "bool" },
|
|
|
|
{ name: "bitMap", type: "ushort" },
|
|
|
|
{ name: "addBitMap", type: "ushort" },
|
|
|
|
{ name: "compressedData", 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: "byte" },
|
|
|
|
{ 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: "byte" },
|
|
|
|
{ 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" }
|
|
|
|
],
|
|
|
|
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" }
|
|
|
|
],
|
|
|
|
0x65: [
|
|
|
|
{ name: "windowId", type: "byte" }
|
|
|
|
],
|
|
|
|
0x66: [
|
|
|
|
{ name: "windowId", type: "byte" },
|
|
|
|
{ name: "slot", type: "short" },
|
|
|
|
{ name: "mouseButton", type: "byte" },
|
|
|
|
{ name: "action", type: "short" },
|
|
|
|
{ name: "shift", type: "bool" },
|
|
|
|
{ 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" }
|
|
|
|
],
|
|
|
|
0xc8: [
|
|
|
|
{ name: "statisticId", type: "int" },
|
|
|
|
{ name: "amount", type: "byte" }
|
|
|
|
],
|
|
|
|
0xc9: [
|
|
|
|
{ name: "playerName", type: "string" },
|
|
|
|
{ name: "online", type: "bool" },
|
|
|
|
{ name: "ping", type: "short" }
|
|
|
|
],
|
|
|
|
0xca: [
|
|
|
|
{ name: "flags", type: "byte" },
|
|
|
|
{ name: "flyingSpeed", type: "byte" },
|
|
|
|
{ name: "walkingSpeed", type: "byte" }
|
|
|
|
],
|
|
|
|
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" }
|
|
|
|
],
|
|
|
|
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: "magic", type: "byte" }
|
|
|
|
],
|
|
|
|
0xff: [
|
|
|
|
{ name: "reason", type: "string" }
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
var writers = {
|
|
|
|
'int': IntWriter,
|
|
|
|
'short': ShortWriter,
|
|
|
|
'byte': ByteWriter,
|
|
|
|
'ubyte': UByteWriter,
|
|
|
|
'string': StringWriter,
|
|
|
|
'byteArray16': ByteArray16Writer,
|
|
|
|
'bool': BoolWriter,
|
|
|
|
'double': DoubleWriter,
|
|
|
|
'float': FloatWriter,
|
|
|
|
'slot': SlotWriter,
|
|
|
|
};
|
|
|
|
|
|
|
|
var readers = {
|
|
|
|
'string': readString,
|
|
|
|
'ascii': readAscii,
|
|
|
|
'byteArray16': readByteArray16,
|
|
|
|
'byteArray32': readByteArray32,
|
|
|
|
'short': readShort,
|
|
|
|
'ushort': readUShort,
|
|
|
|
'int': readInt,
|
|
|
|
'byte': readByte,
|
|
|
|
'ubyte': readUByte,
|
|
|
|
'long': readLong,
|
|
|
|
'slot': readSlot,
|
|
|
|
'bool': readBool,
|
|
|
|
'double': readDouble,
|
|
|
|
'float': readFloat,
|
|
|
|
'slotArray': readSlotArray,
|
|
|
|
'mapChunkBulk': readMapChunkBulk,
|
|
|
|
'entityMetadata': readEntityMetadata,
|
|
|
|
'objectData': readObjectData,
|
|
|
|
'intArray8': readIntArray8,
|
|
|
|
'intVector': readIntVector,
|
|
|
|
'byteVector': readByteVector,
|
|
|
|
'byteVectorArray': readByteVectorArray,
|
|
|
|
};
|
|
|
|
|
|
|
|
var entityMetadataReaders = {
|
|
|
|
0: readByte,
|
|
|
|
1: readShort,
|
|
|
|
2: readInt,
|
|
|
|
3: readFloat,
|
|
|
|
4: readString,
|
|
|
|
5: readSlot,
|
|
|
|
6: readIntVector,
|
|
|
|
};
|
|
|
|
|
|
|
|
function readIntArray8(buffer, offset) {
|
|
|
|
var results = readByte(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
var count = results.value;
|
|
|
|
var cursor = offset + results.size;
|
|
|
|
|
|
|
|
var cursorEnd = cursor + 4 * count;
|
|
|
|
if (cursorEnd > buffer.length) return null;
|
|
|
|
var array = [];
|
|
|
|
for (var i = 0; i < count; ++i) {
|
|
|
|
array.push(buffer.readInt32BE(cursor));
|
|
|
|
cursor += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: array,
|
|
|
|
size: cursorEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function readByteVectorArray(buffer, offset) {
|
|
|
|
var results = readInt(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
var count = results.value;
|
|
|
|
var cursor = offset + results.size;
|
|
|
|
var cursorEnd = cursor + 3 * count;
|
|
|
|
if (cursorEnd > buffer.length) return null;
|
|
|
|
|
|
|
|
var array = [];
|
|
|
|
for (var i = 0; i < count; ++i) {
|
|
|
|
array.push({
|
|
|
|
x: buffer.readInt8(cursor),
|
|
|
|
y: buffer.readInt8(cursor + 1),
|
|
|
|
z: buffer.readInt8(cursor + 2),
|
|
|
|
});
|
|
|
|
cursor += 3;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
value: array,
|
|
|
|
size: cursorEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readByteVector(buffer, offset) {
|
|
|
|
if (offset + 3 > buffer.length) return null;
|
|
|
|
return {
|
|
|
|
value: {
|
|
|
|
x: buffer.readInt8(offset),
|
|
|
|
y: buffer.readInt8(offset + 1),
|
|
|
|
z: buffer.readInt8(offset + 2),
|
|
|
|
},
|
|
|
|
size: 3,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readIntVector(buffer, offset) {
|
|
|
|
if (offset + 12 > buffer.length) return null;
|
|
|
|
return {
|
|
|
|
value: {
|
|
|
|
x: buffer.readInt32BE(offset),
|
|
|
|
y: buffer.readInt32BE(offset + 4),
|
|
|
|
z: buffer.readInt32BE(offset + 8),
|
|
|
|
},
|
|
|
|
size: 12,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readEntityMetadata(buffer, offset) {
|
|
|
|
var cursor = offset;
|
|
|
|
var metadata = {};
|
|
|
|
var item, key, type, results, reader;
|
|
|
|
while (true) {
|
|
|
|
if (cursor + 1 > buffer.length) return null;
|
|
|
|
item = buffer.readUInt8(cursor);
|
|
|
|
cursor += 1;
|
|
|
|
if (item === 0x7f) break;
|
|
|
|
key = item & 0x1f;
|
|
|
|
type = item >> 5;
|
|
|
|
reader = entityMetadataReaders[type];
|
|
|
|
assert.ok(reader, "missing reader for entity metadata type " + type);
|
|
|
|
results = reader(buffer, cursor);
|
|
|
|
if (! results) return null;
|
|
|
|
metadata[key] = results.value;
|
|
|
|
cursor += results.size;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
value: metadata,
|
|
|
|
size: cursor - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readObjectData(buffer, offset) {
|
|
|
|
var cursor = offset + 4;
|
|
|
|
if (cursor > buffer.length) return null;
|
|
|
|
var intField = buffer.readInt32BE(offset);
|
|
|
|
|
|
|
|
if (intField === 0) {
|
|
|
|
return {
|
|
|
|
value: {
|
|
|
|
intField: intField,
|
|
|
|
},
|
|
|
|
size: cursor - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor + 6 > buffer.length) return null;
|
|
|
|
var velocityX = buffer.readInt16BE(cursor);
|
|
|
|
cursor += 2;
|
|
|
|
var velocityY = buffer.readInt16BE(cursor);
|
|
|
|
cursor += 2;
|
|
|
|
var velocityZ = buffer.readInt16BE(cursor);
|
|
|
|
cursor += 2;
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: {
|
|
|
|
intField: intField,
|
|
|
|
velocityX: velocityX,
|
|
|
|
velocityY: velocityY,
|
|
|
|
velocityZ: velocityZ,
|
|
|
|
},
|
|
|
|
size: cursor - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readMapChunkBulk (buffer, offset) {
|
|
|
|
var cursor = offset + 7;
|
|
|
|
if (cursor > buffer.length) return null;
|
|
|
|
var chunkCount = buffer.readInt16BE(offset);
|
|
|
|
var dataSize = buffer.readInt32BE(offset + 2);
|
|
|
|
var skyLightSent = !!buffer.readInt8(offset + 6);
|
|
|
|
|
|
|
|
var cursorEnd = cursor + dataSize + 12 * chunkCount;
|
|
|
|
if (cursorEnd > buffer.length) return null;
|
|
|
|
|
|
|
|
var compressedChunkDataEnd = cursor + dataSize;
|
|
|
|
var compressedChunkData = buffer.slice(cursor, compressedChunkDataEnd);
|
|
|
|
cursor = compressedChunkDataEnd;
|
|
|
|
|
|
|
|
var meta = [];
|
|
|
|
var i, chunkX, chunkZ, primaryBitMap, addBitMap;
|
|
|
|
for (i = 0; i < chunkCount; ++i) {
|
|
|
|
chunkX = buffer.readInt32BE(cursor);
|
|
|
|
cursor += 4;
|
|
|
|
chunkZ = buffer.readInt32BE(cursor);
|
|
|
|
cursor += 4;
|
|
|
|
primaryBitMap = buffer.readUInt16BE(cursor);
|
|
|
|
cursor += 2;
|
|
|
|
addBitMap = buffer.readUInt16BE(cursor);
|
|
|
|
cursor += 2;
|
|
|
|
|
|
|
|
meta.push({
|
|
|
|
chunkX: chunkX,
|
|
|
|
chunkZ: chunkZ,
|
|
|
|
primaryBitMap: primaryBitMap,
|
|
|
|
addBitMap: addBitMap,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: {
|
|
|
|
skyLightSent: skyLightSent,
|
|
|
|
compressedChunkData: compressedChunkData,
|
|
|
|
meta: meta,
|
|
|
|
},
|
|
|
|
size: cursorEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readAscii (buffer, offset) {
|
|
|
|
var results = readShort(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
|
|
|
|
var strBegin = offset + results.size;
|
|
|
|
var strLen = results.value;
|
|
|
|
var strEnd = strBegin + strLen;
|
|
|
|
if (strEnd > buffer.length) return null;
|
|
|
|
var str = buffer.slice(strBegin, strEnd).toString('ascii');
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: str,
|
|
|
|
size: strEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readString (buffer, offset) {
|
|
|
|
var results = readShort(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
|
|
|
|
var strBegin = offset + results.size;
|
|
|
|
var strLen = results.value;
|
|
|
|
var strEnd = strBegin + strLen * 2;
|
|
|
|
if (strEnd > buffer.length) return null;
|
|
|
|
var str = fromUcs2.convert(buffer.slice(strBegin, strEnd)).toString('utf8');
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: str,
|
|
|
|
size: strEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readByteArray16 (buffer, offset) {
|
|
|
|
var results = readShort(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
|
|
|
|
var bytesBegin = offset + results.size;
|
|
|
|
var bytesSize = results.value;
|
|
|
|
var bytesEnd = bytesBegin + bytesSize;
|
|
|
|
if (bytesEnd > buffer.length) return null;
|
|
|
|
var bytes = buffer.slice(bytesBegin, bytesEnd);
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: bytes,
|
|
|
|
size: bytesEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readByteArray32(buffer, offset) {
|
|
|
|
var results = readInt(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
|
|
|
|
var bytesBegin = offset + results.size;
|
|
|
|
var bytesSize = results.value;
|
|
|
|
var bytesEnd = bytesBegin + bytesSize;
|
|
|
|
if (bytesEnd > buffer.length) return null;
|
|
|
|
var bytes = buffer.slice(bytesBegin, bytesEnd);
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: bytes,
|
|
|
|
size: bytesEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readSlotArray (buffer, offset) {
|
|
|
|
var results = readShort(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
var count = results.value;
|
|
|
|
var cursor = offset + results.size;
|
|
|
|
|
|
|
|
var slotArray = [];
|
|
|
|
for (var i = 0; i < count; ++i) {
|
|
|
|
results = readSlot(buffer, cursor);
|
|
|
|
if (! results) return null;
|
|
|
|
slotArray.push(results.value);
|
|
|
|
cursor += results.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: slotArray,
|
|
|
|
size: cursor - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readShort(buffer, offset) {
|
|
|
|
if (offset + 2 > buffer.length) return null;
|
|
|
|
var value = buffer.readInt16BE(offset);
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
size: 2,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readUShort(buffer, offset) {
|
|
|
|
if (offset + 2 > buffer.length) return null;
|
|
|
|
var value = buffer.readUInt16BE(offset);
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
size: 2,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readInt(buffer, offset) {
|
|
|
|
if (offset + 4 > buffer.length) return null;
|
|
|
|
var value = buffer.readInt32BE(offset);
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
size: 4,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readFloat(buffer, offset) {
|
|
|
|
if (offset + 4 > buffer.length) return null;
|
|
|
|
var value = buffer.readFloatBE(offset);
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
size: 4,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readDouble(buffer, offset) {
|
|
|
|
if (offset + 8 > buffer.length) return null;
|
|
|
|
var value = buffer.readDoubleBE(offset);
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
size: 8,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readLong(buffer, offset) {
|
|
|
|
if (offset + 8 > buffer.length) return null;
|
|
|
|
return {
|
|
|
|
value: [buffer.readInt32BE(offset), buffer.readInt32BE(offset + 4)],
|
|
|
|
size: 8,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readByte(buffer, offset) {
|
|
|
|
if (offset + 1 > buffer.length) return null;
|
|
|
|
var value = buffer.readInt8(offset);
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
size: 1,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readUByte(buffer, offset) {
|
|
|
|
if (offset + 1 > buffer.length) return null;
|
|
|
|
var value = buffer.readUInt8(offset);
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
size: 1,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readBool(buffer, offset) {
|
|
|
|
if (offset + 1 > buffer.length) return null;
|
|
|
|
var value = buffer.readInt8(offset);
|
|
|
|
return {
|
|
|
|
value: !!value,
|
|
|
|
size: 1,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function readSlot(buffer, offset) {
|
|
|
|
var results = readShort(buffer, offset);
|
|
|
|
if (! results) return null;
|
|
|
|
var blockId = results.value;
|
|
|
|
var cursor = offset + results.size;
|
|
|
|
|
|
|
|
if (blockId === -1) {
|
|
|
|
return {
|
|
|
|
value: { id: blockId },
|
|
|
|
size: cursor - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var cursorEnd = cursor + 5;
|
|
|
|
if (cursorEnd > buffer.length) return null;
|
|
|
|
var itemCount = buffer.readInt8(cursor);
|
|
|
|
var itemDamage = buffer.readInt16BE(cursor + 1);
|
|
|
|
var nbtDataSize = buffer.readInt16BE(cursor + 3);
|
|
|
|
if (nbtDataSize === -1) nbtDataSize = 0;
|
|
|
|
var nbtDataEnd = cursorEnd + nbtDataSize;
|
|
|
|
var nbtData = buffer.slice(cursorEnd, nbtDataEnd);
|
|
|
|
|
|
|
|
return {
|
|
|
|
value: {
|
|
|
|
id: blockId,
|
|
|
|
itemCount: itemCount,
|
|
|
|
itemDamage: itemDamage,
|
|
|
|
nbtData: nbtData,
|
|
|
|
},
|
|
|
|
size: nbtDataEnd - offset,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function SlotWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.size = value.id === -1 ? 2 : 7 + this.value.nbtData.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
SlotWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeInt16BE(this.value.id, offset);
|
|
|
|
if (this.value.id === -1) return;
|
|
|
|
buffer.writeInt8(this.value.itemCount, offset + 2);
|
|
|
|
buffer.writeInt16BE(this.value.itemDamage, offset + 3);
|
|
|
|
var nbtDataSize = this.value.nbtData.length;
|
|
|
|
if (nbtDataSize === 0) nbtDataSize = -1; // I don't know wtf mojang smokes
|
|
|
|
buffer.writeInt16BE(nbtDataSize, offset + 5);
|
|
|
|
this.value.nbtData.copy(buffer, offset + 7);
|
|
|
|
};
|
|
|
|
|
|
|
|
function StringWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.encoded = toUcs2.convert(value);
|
|
|
|
this.size = 2 + this.encoded.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
StringWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeInt16BE(this.value.length, offset);
|
|
|
|
this.encoded.copy(buffer, offset + 2);
|
|
|
|
};
|
|
|
|
|
|
|
|
function ByteArray16Writer(value) {
|
|
|
|
assert.ok(Buffer.isBuffer(value), "non buffer passed to ByteArray16Writer");
|
|
|
|
this.value = value;
|
|
|
|
this.size = 2 + value.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
ByteArray16Writer.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeInt16BE(this.value.length, offset);
|
|
|
|
this.value.copy(buffer, offset + 2);
|
|
|
|
};
|
|
|
|
|
|
|
|
function ByteWriter(value) {
|
2013-01-04 01:45:57 -05:00
|
|
|
this.value = value == null ? 0 : value;
|
2013-01-03 23:14:19 -05:00
|
|
|
this.size = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ByteWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeInt8(this.value, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
function BoolWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.size = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
BoolWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeInt8(this.value ? 1 : 0, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
function UByteWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.size = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
UByteWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeUInt8(this.value, offset);
|
|
|
|
};
|
|
|
|
|
|
|
|
function FloatWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.size = 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
FloatWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeFloatBE(this.value, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
function DoubleWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.size = 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
DoubleWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeDoubleBE(this.value, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
function ShortWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.size = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
ShortWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeInt16BE(this.value, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
function IntWriter(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.size = 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
IntWriter.prototype.write = function(buffer, offset) {
|
|
|
|
buffer.writeInt32BE(this.value, offset);
|
|
|
|
}
|
|
|
|
|
2013-01-04 01:45:57 -05:00
|
|
|
function get(packetId, toServer) {
|
|
|
|
var packetInfo = packets[packetId];
|
|
|
|
return Array.isArray(packetInfo) ?
|
|
|
|
packetInfo :
|
|
|
|
toServer ?
|
|
|
|
packetInfo.toServer :
|
|
|
|
packetInfo.toClient;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createPacketBuffer(packetId, params, isServer) {
|
2013-01-03 23:14:19 -05:00
|
|
|
var size = 1;
|
|
|
|
var fields = [ new UByteWriter(packetId) ];
|
2013-01-04 01:45:57 -05:00
|
|
|
var packet = get(packetId, isServer);
|
2013-01-03 23:14:19 -05:00
|
|
|
packet.forEach(function(fieldInfo) {
|
|
|
|
var value = params[fieldInfo.name];
|
|
|
|
var Writer = writers[fieldInfo.type];
|
|
|
|
assert.ok(Writer, "missing writer for data type: " + fieldInfo.type);
|
|
|
|
var field = new Writer(value);
|
|
|
|
size += field.size;
|
|
|
|
fields.push(field);
|
|
|
|
});
|
|
|
|
var buffer = new Buffer(size);
|
|
|
|
var cursor = 0;
|
|
|
|
fields.forEach(function(field) {
|
|
|
|
field.write(buffer, cursor);
|
|
|
|
cursor += field.size;
|
|
|
|
});
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
2013-01-04 01:45:57 -05:00
|
|
|
function parsePacket(buffer, isServer) {
|
2013-01-03 23:14:19 -05:00
|
|
|
if (buffer.length < 1) return null;
|
|
|
|
var packetId = buffer.readUInt8(0);
|
|
|
|
var size = 1;
|
|
|
|
var results = { id: packetId };
|
2013-01-04 01:45:57 -05:00
|
|
|
var packetInfo = get(packetId, !isServer);
|
2013-01-03 23:14:19 -05:00
|
|
|
assert.ok(packetInfo, "Unrecognized packetId: " + packetId);
|
|
|
|
var i, fieldInfo, read, readResults;
|
|
|
|
for (i = 0; i < packetInfo.length; ++i) {
|
|
|
|
fieldInfo = packetInfo[i];
|
|
|
|
read = readers[fieldInfo.type];
|
|
|
|
assert.ok(read, "missing reader for data type: " + fieldInfo.type);
|
|
|
|
readResults = read(buffer, size);
|
|
|
|
if (readResults) {
|
|
|
|
results[fieldInfo.name] = readResults.value;
|
|
|
|
size += readResults.size;
|
|
|
|
} else {
|
|
|
|
// buffer needs to be more full
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
size: size,
|
|
|
|
results: results,
|
|
|
|
};
|
|
|
|
}
|