├── .gitignore ├── AUTHORS ├── examples ├── helloworld.coffee ├── perv.coffee ├── ping.coffee └── debug.coffee ├── src ├── offline_client.coffee ├── connection.coffee ├── packet.coffee ├── online_client.coffee ├── client.coffee ├── parser.coffee └── protocol.coffee ├── package.json ├── index.coffee ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Chris Lloyd (http://chrislloyd.com.au) 2 | Dave Newman -------------------------------------------------------------------------------- /examples/helloworld.coffee: -------------------------------------------------------------------------------- 1 | # Logs in and says hello. 2 | # $ coffee examples/helloworld.coffee pluto.minefold.com 25565 whatupdave password 3 | 4 | mc = require('..') 5 | [_, _, host, port, username, password] = process.argv 6 | 7 | mc.createOnlineClient host, port, username, password, (bot) -> 8 | bot.say 'Hello world!', -> 9 | bot.end() 10 | -------------------------------------------------------------------------------- /examples/perv.coffee: -------------------------------------------------------------------------------- 1 | # Logs in and listens to chat. 2 | # $ coffee examples/perv.coffee pluto.minefold.com 25565 whatupdave password 3 | 4 | mc = require('..') 5 | [_, _, host, port, username, password] = process.argv 6 | 7 | mc.createOnlineClient host, port, username, password, (perv) -> 8 | console.warn "#{username} connected to #{host}:#{port}" 9 | 10 | perv.on 'chat', (msg) -> 11 | console.log msg 12 | -------------------------------------------------------------------------------- /src/offline_client.coffee: -------------------------------------------------------------------------------- 1 | Client = require('./client').Client 2 | 3 | class exports.OfflineClient extends Client 4 | 5 | @createConnection: -> 6 | client = super(arguments...) 7 | 8 | # Start things off by initializing the handshake 9 | client.write 'handshake', client.username 10 | 11 | # Once the server handshake is compelted, initiate a login. 12 | client.once 'handshake', (serverId) => 13 | client.write 'login', Client.PROTOCOL_VERSION, client.username, 0, '', 0, 0, 0, 0, 0 14 | 15 | client 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-minecraft", 3 | "version": "0.0.4", 4 | "description": "A node.js implementation of the Minecraft protocol", 5 | "homepage": "https://github.com/minefold/node-minecraft", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/minefold/node-minecraft.git" 9 | }, 10 | "main": "./index", 11 | "dependencies": { 12 | "iconv": "~1.1.3", 13 | "node-int64": "~0.3.0" 14 | }, 15 | "devDependencies": { 16 | "coffee-script": "~1.2.0" 17 | }, 18 | "engines": { 19 | "node": "*" 20 | } 21 | } -------------------------------------------------------------------------------- /index.coffee: -------------------------------------------------------------------------------- 1 | mc = 2 | Client: require('./src/client').Client 3 | OnlineClient: require('./src/online_client').OnlineClient 4 | OfflineClient: require('./src/offline_client').OfflineClient 5 | Parser: require('./src/parser').Parser 6 | Packet: require('./src/packet').Packet 7 | Protocol: require('./src/protocol') 8 | Connection: require('./src/connection').Connection 9 | 10 | mc.createOnlineClient = -> 11 | mc.OnlineClient.createConnection(arguments...) 12 | 13 | mc.createOfflineClient = -> 14 | mc.OfflineClient.createConnection(arguments...) 15 | 16 | # Playing nice with Mojang :) 17 | mc.createClient = mc.createOnlineClient 18 | 19 | module.exports = mc 20 | -------------------------------------------------------------------------------- /examples/ping.coffee: -------------------------------------------------------------------------------- 1 | # Pings a Minecraft server 2 | # 3 | # $ coffee examples/ping.coffee pluto.minefold.com 4 | # ping: 194ms 5 | 6 | net = require 'net' 7 | mc = require('..') 8 | 9 | [_, _, hostname, port] = process.argv 10 | port or= mc.Client.PORT 11 | 12 | # Start the timer 13 | console.time('ping') 14 | 15 | # Open a connection to the server 16 | socket = net.createConnection port, hostname 17 | socket.on 'connect', -> 18 | c = new mc.Connection(socket) 19 | 20 | # Write out the ping packet 21 | c.writePacket(0xFE) 22 | 23 | # Stop the timer when we get a response 24 | c.once 'data', (header, payload) -> 25 | [msg, players, max] = payload[0].split('§', 3) 26 | console.log "msg:", msg 27 | console.log "players:", players 28 | console.log "max:", max 29 | 30 | console.timeEnd('ping') 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Chris Lloyd & Dave Newman. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /examples/debug.coffee: -------------------------------------------------------------------------------- 1 | # Inspects packets between a client and a server. This is really useful for inspecting the packet stream between a client and a server. 2 | # $ coffee examples/proxy 0.0.0.0 25565 5001 3 | 4 | net = require 'net' 5 | url = require 'url' 6 | mc = require('..') 7 | 8 | [_, _, serverPort, clientPort] = process.argv 9 | 10 | proxy = net.createServer() 11 | proxy.on 'connection', (client) -> 12 | console.log '-| ', client.address() 13 | 14 | server = new net.Socket() 15 | server.connect serverPort, -> 16 | console.log ' |-', server.address() 17 | 18 | client.on 'close', (had_error) -> 19 | console.log 'x| ' 20 | server.end() 21 | 22 | server.on 'close', (had_error) -> 23 | console.log ' |x' 24 | client.end() 25 | 26 | serverConn = new mc.Connection(server) 27 | clientConn = new mc.Connection(client) 28 | 29 | clientConn.on "data", (header, payload) -> 30 | console.log '>| ', "[0x#{header.toString(16)}]", payload... 31 | 32 | client.on 'data', (data) -> 33 | server.write(data) 34 | 35 | serverConn.on "data", (header, payload) -> 36 | console.log ' |<', "[0x#{header.toString(16)}]", payload... 37 | 38 | server.on 'data', (data) -> 39 | client.write(data) 40 | 41 | proxy.listen clientPort, -> 42 | console.log "listening on #{clientPort} (proxying to #{serverPort})" 43 | -------------------------------------------------------------------------------- /src/connection.coffee: -------------------------------------------------------------------------------- 1 | events = require 'events' 2 | 3 | Parser = require('./parser').Parser 4 | Packet = require('./packet').Packet 5 | Protocol = require('./protocol') 6 | 7 | class exports.Connection extends events.EventEmitter 8 | constructor: (@socket) -> 9 | @parser = new Parser() 10 | @socket.on 'connect', => @emit 'connect' 11 | @socket.on 'end', => @emit 'end' 12 | @socket.on 'close', (hadError) => @emit 'close', hadError 13 | @socket.on 'error', (error) => @emit 'error', error 14 | @socket.on 'data', (data) => @addData(data) 15 | 16 | end: (msg) -> 17 | @socket.end() 18 | 19 | writePacket: (header, payload...) -> 20 | if payload? and typeof(payload[payload.length - 1]) is 'function' 21 | callback = payload.pop() 22 | 23 | packet = new Packet(header) 24 | @socket.write packet.build(payload...), callback 25 | 26 | addData: (data) -> 27 | # If leftover data already exists, concatenate. 28 | @packet = if @packet? 29 | p = new Buffer(@packet.length + data.length) 30 | @packet.copy(p, 0, 0) 31 | data.copy(p, @packet.length, 0) 32 | p 33 | else 34 | data 35 | 36 | @parsePacket() 37 | 38 | parsePacket: -> 39 | try 40 | [bytesParsed, header, payload] = @parser.parse(@packet) 41 | 42 | # Cut out the packet we parsed 43 | @packet = if bytesParsed < @packet.length 44 | @packet.slice(bytesParsed) 45 | else 46 | null 47 | 48 | @emit 'data', header, payload 49 | 50 | # Parse the reset of the data if any is remaining. 51 | @parsePacket() if @packet? 52 | 53 | # An error parsing means the data crosses over two packets and we need to try again when another packet comes in. 54 | catch e 55 | @parser.rewind() 56 | -------------------------------------------------------------------------------- /src/packet.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | Iconv = require('iconv').Iconv 3 | Protocol = require('./protocol') 4 | 5 | class exports.Packet 6 | @sizeOfByte = -> 1 7 | @sizeOfShort = -> 2 8 | @sizeOfInt = -> 4 9 | @sizeOfLong = -> @sizeOfDouble() 10 | @sizeOfFloat = -> 4 11 | @sizeOfDouble = -> 8 12 | @sizeOfBool = -> 1 13 | @sizeOfStr = (str) -> @sizeOfShort() + Buffer.byteLength(str, 'ucs2') 14 | 15 | constructor: (@header) -> 16 | @idx = 0 17 | @schema = Protocol.SCHEMAS[@header] 18 | @iconv = new Iconv('UTF-8', 'UCS-2BE') 19 | 20 | sizeOf: (payload) -> 21 | (@constructor["sizeOf#{type}"](payload[i]) for type, i in @schema) 22 | .reduce ((c, n) -> c + n), @constructor.sizeOfByte() 23 | 24 | writeByte: (byte) -> 25 | @buf.writeUInt8(byte, @idx) 26 | @idx += @constructor.sizeOfByte() 27 | 28 | writeShort: (short) -> 29 | @buf.writeInt16BE(short, @idx) 30 | @idx += @constructor.sizeOfShort() 31 | 32 | writeInt: (int) -> 33 | @buf.writeInt32BE(int, @idx) 34 | @idx += @constructor.sizeOfInt() 35 | 36 | writeLong: (longStr) -> 37 | @writeDouble(parseInt(longStr, 10)) 38 | 39 | writeFloat: (float) -> 40 | @buf.writeFloatBE(float, @idx) 41 | @idx += @constructor.sizeOfFloat() 42 | 43 | writeDouble: (double) -> 44 | @buf.writeDoubleBE(double, @idx) 45 | @idx += @constructor.sizeOfDouble() 46 | 47 | writeStr: (str) -> 48 | @writeShort(str.length) 49 | 50 | strBuf = @iconv.convert(str) 51 | strBuf.copy(@buf, @idx, 0) 52 | 53 | @idx += strBuf.length 54 | 55 | 56 | build: (payload...) -> 57 | @buf = new Buffer(@sizeOf(payload)) 58 | 59 | @writeByte(@header) 60 | 61 | for type, i in @schema 62 | @["write#{type}"](payload[i]) 63 | 64 | @idx = 0 65 | @buf 66 | -------------------------------------------------------------------------------- /src/online_client.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | url = require 'url' 3 | Client = require('./client').Client 4 | 5 | class exports.OnlineClient extends Client 6 | @LAUNCHER_VERSION = 12 7 | 8 | @createConnection: -> 9 | client = super(arguments...) 10 | 11 | # Get the session token 12 | client.getSessionId (sessionId) -> 13 | 14 | # Start the handshake 15 | client.write 'handshake', client.username 16 | 17 | # Once the handshake is complete 18 | client.once 'handshake', (serverId) -> 19 | # Verify the server with our session token 20 | client.verifyServer sessionId, serverId, -> 21 | 22 | # If everything is kosher, login 23 | client.write 'login', Client.PROTOCOL_VERSION, client.username, 0, '', 0, 0, 0, 0, 0 24 | 25 | client 26 | 27 | getSessionId: (callback) -> 28 | query = {user: @username, password: @password, version: @constructor.LAUNCHER_VERSION} 29 | 30 | options = 31 | hostname: 'login.minecraft.net' 32 | path: url.format(pathname: '/', query: query) 33 | 34 | http.get options, (rep) -> 35 | rep.on 'data', (data) -> 36 | body = data.toString() 37 | 38 | if body is 'Bad login' 39 | @emit 'error', body 40 | else 41 | callback body.split(':', 4)[3] 42 | 43 | verifyServer: (sessionId, serverId, callback) -> 44 | query = {user: @username, sessionId: sessionId, serverId: serverId} 45 | 46 | options = 47 | hostname: 'session.minecraft.net' 48 | path: url.format(pathname: '/game/joinserver.jsp', query: query) 49 | 50 | http.get options, (rep) -> 51 | rep.on 'data', (data) -> 52 | body = data.toString() 53 | 54 | if body isnt 'OK' 55 | @emit 'error', body 56 | else 57 | callback() 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-minecraft 2 | 3 | by [Chris Lloyd](http://github.com/chrislloyd) & [Dave Newman](http://github.com/whatupdave) @ [Minefold](https://minefold.com) 4 | 5 | Provides an implementation of the Minecraft protocol for Node.js that can be used for both client and server hacking. 6 | 7 | 8 | ## Installation 9 | 10 | You need [Node.js](nodejs.org) and [NPM](npmjs.org) installed first. 11 | 12 | $ npm install node-minecraft 13 | 14 | 15 | ## Writing Bots 16 | 17 | It's best to check out `examples/helloworld.coffee` and `examples/perv.coffee`. They are both small bots that successfully connect to a world and serve some function. Let's run through a simple echo bot. All it does is repeat any chat messages it hears. 18 | 19 | # Loads the library 20 | mc = require 'node-minecraft' 21 | 22 | # This creates a new client 23 | mc.createOnlineClient host, port, username, password, (bot) -> 24 | 25 | # 'chat' is an event that is fired when a chat message is recieved 26 | bot.on 'chat', (msg) -> 27 | # `say` writes a string back for everybody to see. 28 | bot.say msg 29 | 30 | Dead simple! 31 | 32 | ## FAQ 33 | 34 | ### Why `createOnlineClient`? 35 | 36 | Minecraft checks that you have bought a copy of the game before you can play online. This why the library needs your password. You'll need to use that method if you are putting your bot into public servers who are in `online-mode`. 37 | 38 | If you have a local server that is in offline mode you can use the `createOfflineClient` that doesn't validate with Mojang. This is best for testing and spinning up lots of bots locally. 39 | 40 | ### Why does the bot just float there? 41 | 42 | Minecraft works by each client calculating where it is suppose to be. The bot at the moment is too dumb to know about gravity, so it doesn't move. However, in future versions of `node-minecraft` we want to make the bots more intelligent. Perhaps you could contribute that? 43 | -------------------------------------------------------------------------------- /src/client.coffee: -------------------------------------------------------------------------------- 1 | events = require 'events' 2 | net = require 'net' 3 | Connection = require('./connection').Connection 4 | Protocol = require('./protocol') 5 | 6 | class exports.Client extends events.EventEmitter 7 | @PORT = 25565 8 | @PROTOCOL_VERSION = 28 9 | 10 | @createConnection: (host, port, username, password, callback) -> 11 | new @(host, port, username, password, callback) 12 | 13 | constructor: (@host, @port, @username, @password, callback) -> 14 | if callback? and typeof(callback) is 'function' 15 | @on 'connect', callback 16 | 17 | @conn = new Connection(net.createConnection(@port, @host)) 18 | 19 | @conn.on 'end', => @emit 'end' 20 | @conn.on 'close', (hadError) => @emit 'close', hadError 21 | @conn.on 'error', (error) => @emit 'error', error 22 | 23 | # Emits a human readable event with the payload as args 24 | @conn.on 'data', (header, payload) => 25 | eventName = Protocol.LABELS[header] || 'unhandled' 26 | @emit eventName, payload... 27 | 28 | # Echo keepalives to keep the socket open 29 | @on 'keepalive', => 30 | @write 'keepalive', arguments... 31 | 32 | # Echos the 0x0D packet (needs to happen otherwise server fucks out) 33 | @once 'player position and look', => 34 | @write 'player position and look', arguments... 35 | 36 | # Kick things off once when we get the login 37 | @once 'login', (@eId, _, seed, levelType, mode, dim, difficulty, height, maxPlayers) -> 38 | @world = 39 | seed: seed 40 | levelType: levelType 41 | mode: mode 42 | dimension: dim 43 | difficulty: difficulty 44 | maxPlayers: maxPlayers 45 | 46 | @emit 'connect', @ 47 | 48 | 49 | write: (packetName, payload...) -> 50 | packet = Protocol.HEADERS[packetName] 51 | @conn.writePacket packet, payload... 52 | 53 | end: (msg) -> 54 | @write 'kick', msg || 'Bye!', => 55 | @conn.end() 56 | 57 | say: (msg, callback) -> 58 | console.assert 0 < msg.length <= 100 59 | @write 'chat', msg, callback 60 | 61 | -------------------------------------------------------------------------------- /src/parser.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | Iconv = require('iconv').Iconv 3 | Int64 = require('node-int64') 4 | Protocol = require('./protocol') 5 | 6 | class exports.Parser 7 | constructor: -> 8 | @idx = 0 9 | @iconv = new Iconv('UCS-2BE', 'UTF-8') 10 | 11 | readByte: -> 12 | byte = @buf.readUInt8(@idx) 13 | @idx += 1 14 | byte 15 | 16 | readShort: -> 17 | short = @buf.readInt16BE(@idx) 18 | @idx += 2 19 | short 20 | 21 | readInt: -> 22 | int = @buf.readInt32BE(@idx) 23 | @idx += 4 24 | int 25 | 26 | readLong: -> 27 | inta = @buf.readInt32LE(@idx) 28 | @idx += 4 29 | intb = @buf.readInt32LE(@idx) 30 | @idx += 4 31 | (new Int64(intb, inta)).toOctetString() 32 | 33 | readFloat: -> 34 | float = @buf.readFloatBE(@idx) 35 | @idx += 4 36 | float 37 | 38 | readDouble: -> 39 | double = @buf.readDoubleBE(@idx) 40 | @idx += 8 41 | double 42 | 43 | readStr: -> 44 | chars = @readShort() 45 | # UCS-2 chars are 16bit 46 | bytes = chars * 2 47 | 48 | assert(0 <= chars <= 240, "string length is #{chars}") 49 | raw = @buf.slice(@idx, @idx + bytes) 50 | str = @iconv.convert(raw).toString() 51 | @idx += bytes 52 | str 53 | 54 | readBool: -> 55 | bool = @readByte() 56 | 57 | assert(bool is 0 or bool is 1, 'bool out of range') 58 | 59 | bool > 0 60 | 61 | readMetadata: (metadata=[]) -> 62 | byte = @readByte() 63 | metadata.push(byte) 64 | if byte isnt 127 65 | @readMetadata(metadata) 66 | else 67 | return metadata 68 | 69 | readSlot: -> 70 | item = @readShort() 71 | 72 | if item is -1 73 | 'empty' 74 | else 75 | count = @readByte() 76 | damage = @readShort() 77 | 78 | # TODO Skips over NBT data 79 | if item in Protocol.ENCHANTABLES 80 | bytes = @readShort() 81 | assert(@idx + bytes < @buf.length, 'slot out of range') 82 | @idx += bytes 83 | 84 | {item: item, count: count, damage: damage} 85 | 86 | readSlots: -> 87 | length = @readShort() 88 | # Short circuit early 89 | assert(length <= @buf.length) 90 | 91 | @readSlot() for i in [0...length] 92 | 93 | # TODO Include Buffer of Gzip'd NBT data 94 | readChunk: -> 95 | bytes = @readInt() 96 | 97 | # Explicit bounds check as data isn't actually read 98 | assert(@idx + bytes < @buf.length, 'chunk out of range') 99 | 100 | @idx += bytes 101 | ['chunk', bytes] 102 | 103 | readBlockChanges: -> 104 | length = @readShort() 105 | coords = for i in [0...length] 106 | @readShort() 107 | 108 | types = for i in [0...length] 109 | @readByte() 110 | 111 | metadata = for i in [0...length] 112 | @readByte() 113 | 114 | [coords, types, metadata] 115 | 116 | 117 | readRecord: -> 118 | length = @readInt() 119 | for i in [0...length] 120 | [@readByte(), @readByte(), @readByte()] 121 | 122 | readText: -> 123 | length = @readByte() 124 | text = @buf.toString('ascii', @idx, @idx+length) 125 | @idx += length 126 | text 127 | 128 | 129 | rewind: -> 130 | @idx = 0 131 | 132 | parse: (@buf) -> 133 | header = @readByte() 134 | schema = Protocol.SCHEMAS[header] 135 | 136 | unless schema? 137 | console.log 'unknown schema: ', header.toString('hex'), @buf 138 | process.exit(1) 139 | 140 | payload = for fieldType in schema 141 | @["read#{fieldType}"]() 142 | 143 | bytesRead = @idx 144 | @idx = 0 145 | 146 | [bytesRead, header, payload] 147 | -------------------------------------------------------------------------------- /src/protocol.coffee: -------------------------------------------------------------------------------- 1 | exports.LABELS = 2 | 0x00: 'keepalive' 3 | 0x01: 'login' 4 | 0x02: 'handshake' 5 | 0x03: 'chat' 6 | 0x04: 'time update' 7 | 0x05: 'entity equipment' 8 | 0x06: 'spawn position' 9 | 0x07: 'use entity' 10 | 0x08: 'update health' 11 | 0x09: 'respawn' 12 | 0x0A: 'player' 13 | 0x0B: 'player position' 14 | 0x0C: 'player look' 15 | 0x0D: 'player position and look' 16 | 0x0E: 'player digging' 17 | 0x0F: 'player block placement' 18 | 0x10: 'holding change' 19 | 0x11: 'use bed' 20 | 0x12: 'animation' 21 | 0x13: 'entity action' 22 | 0x14: 'named entity spawn' 23 | 0x15: 'pickup spawn' 24 | 0x16: 'collect item' 25 | 0x17: 'add object' 26 | 0x18: 'mob spawn' 27 | 0x19: 'painting' 28 | 0x1A: 'experience orb' 29 | 0x1B: 'stance' 30 | 0x1C: 'entity velocity' 31 | 0x1D: 'destroy entity' 32 | 0x1E: 'entity' 33 | 0x1F: 'entity move' 34 | 0x20: 'entity look' 35 | 0x21: 'entity look and move' 36 | 0x22: 'entity teleport' 37 | 0x26: 'entity status' 38 | 0x27: 'attach entity' 39 | 0x28: 'entity metadata' 40 | 0x29: 'entity effect' 41 | 0x2A: 'remove entity effect' 42 | 0x2B: 'experience' 43 | 0x32: 'pre-chunk' 44 | 0x33: 'map chunk' 45 | 0x34: 'multi block change' 46 | 0x35: 'block change' 47 | 0x36: 'block action' 48 | 0x3C: 'explosion' 49 | 0x3D: 'effect' 50 | 0x46: 'clear state' 51 | 0x47: 'thunderbolt' 52 | 0x64: 'open window' 53 | 0x65: 'close window' 54 | 0x66: 'window click' 55 | 0x67: 'set slot' 56 | 0x68: 'window items' 57 | 0x69: 'update window property' 58 | 0x6A: 'transaction' 59 | 0x6B: 'inventory action' 60 | 0x6C: 'enchant item' 61 | 0x82: 'update sign' 62 | 0x83: 'item data' 63 | 0xC8: 'increment stat' 64 | 0xC9: 'player list item' 65 | 0xFE: 'ping' 66 | 0xFF: 'kick' 67 | 68 | h = {} 69 | for header, label of exports.LABELS 70 | h[label] = parseInt(header, 10) 71 | 72 | exports.HEADERS = h 73 | 74 | exports.SCHEMAS = 75 | 0x00: ['Int'] 76 | 0x01: ['Int', 'Str', 'Long', 'Str', 'Int', 'Byte', 'Byte', 'Byte', 'Byte'] 77 | 0x02: ['Str'] 78 | 0x03: ['Str'] 79 | 0x04: ['Long'] 80 | 0x05: ['Int', 'Short', 'Short', 'Short'] 81 | 0x06: ['Int', 'Int', 'Int'] 82 | 0x07: ['Int', 'Int', 'Bool'] 83 | 0x08: ['Short', 'Short', 'Float'] 84 | 0x09: ['Byte', 'Byte', 'Byte', 'Short', 'Long'] 85 | 0x0A: ['Bool'] 86 | 0x0B: ['Double', 'Double', 'Double', 'Double', 'Bool'] 87 | 0x0C: ['Float', 'Float', 'Bool'] 88 | 0x0D: ['Double', 'Double', 'Double', 'Double', 'Float', 'Float', 'Bool'] 89 | 0x0E: ['Byte', 'Int', 'Byte', 'Int', 'Byte'] 90 | 0x0F: ['Int', 'Byte', 'Int', 'Byte', 'Slot'] 91 | 0x10: ['Short'] 92 | 0x11: ['Int', 'Byte', 'Int', 'Byte', 'Int'] 93 | 0x12: ['Int', 'Byte'] 94 | 0x13: ['Int', 'Byte'] 95 | 0x14: ['Int', 'Str', 'Int', 'Int', 'Int', 'Byte', 'Byte', 'Short'] 96 | 0x15: ['Int', 'Short', 'Byte', 'Short', 'Int', 'Int', 'Int', 'Byte', 'Byte', 'Byte'] 97 | 0x16: ['Int', 'Int'] 98 | 0x17: ['Int', 'Byte', 'Int', 'Int', 'Int', 'Int', 'Short', 'Short', 'Short'] 99 | 0x18: ['Int', 'Byte', 'Int', 'Int', 'Int', 'Byte', 'Byte', 'Metadata'] 100 | 0x19: ['Int', 'Str', 'Int', 'Int', 'Int', 'Int'] 101 | 0x1A: ['Int', 'Int', 'Int', 'Int', 'Short'] 102 | 0x1B: ['Float', 'Float', 'Float', 'Float', 'Bool', 'Bool'] 103 | 0x1C: ['Int', 'Short', 'Short', 'Short'] 104 | 0x1D: ['Int'] 105 | 0x1E: ['Int'] 106 | 0x1F: ['Int', 'Byte', 'Byte', 'Byte'] 107 | 0x20: ['Int', 'Byte', 'Byte'] 108 | 0x21: ['Int', 'Byte', 'Byte', 'Byte', 'Byte', 'Byte'] 109 | 0x22: ['Int', 'Int', 'Int', 'Int', 'Byte', 'Byte'] 110 | 0x26: ['Int', 'Byte'] 111 | 0x27: ['Int', 'Int'] 112 | 0x28: ['Int', 'Metadata'] 113 | 0x29: ['Int', 'Byte', 'Byte', 'Short'] 114 | 0x2A: ['Int', 'Byte'] 115 | 0x2B: ['Float', 'Short', 'Short'] 116 | 0x32: ['Int', 'Int', 'Bool'] 117 | 0x33: ['Int', 'Short', 'Int', 'Byte', 'Byte', 'Byte', 'Chunk'] 118 | 0x34: ['Int', 'Int', 'BlockChanges'] 119 | 0x35: ['Int', 'Byte', 'Int', 'Byte', 'Byte'] 120 | 0x36: ['Int', 'Short', 'Int', 'Byte', 'Byte'] 121 | 0x3C: ['Double', 'Double', 'Double', 'Float', 'Record'] 122 | # Parsing of this could be more useful 123 | 0x3D: ['Int', 'Int', 'Byte', 'Int', 'Int'] 124 | 0x46: ['Byte', 'Byte'] 125 | 0x47: ['Int', 'Boolean', 'Int', 'Int', 'Int'] 126 | 0x64: ['Byte', 'Byte', 'Str', 'Byte'] 127 | 0x65: ['Byte'] 128 | # Wiki says right click is byte, should be short. 129 | 0x66: ['Byte', 'Short', 'Bool', 'Short', 'Bool', 'Slot'] 130 | 0x67: ['Byte', 'Short', 'Slot'] 131 | 0x68: ['Byte', 'Slots'] 132 | 0x69: ['Byte', 'Short', 'Short'] 133 | 0x6A: ['Byte', 'Short', 'Bool'] 134 | 0x6B: ['Short', 'Slot'] 135 | 0x6C: ['Byte', 'Byte'] 136 | 0x82: ['Int', 'Short', 'Int', 'Str', 'Str', 'Str', 'Str'] 137 | 0x83: ['Short', 'Short', 'Text'] 138 | 0xC8: ['Int', 'Byte'] 139 | 0xC9: ['Str', 'Bool', 'Short'] 140 | 0xFE: [] 141 | 0xFF: ['Str'] 142 | 143 | exports.ENCHANTABLES = [ 144 | # Flint, Bow, Rod, Shears 145 | 0x103, 0x105, 0x15A, 0x167, 146 | # Tools 147 | 0x10C, 0x10D, 0x10E, 0x10F, 0x122, 148 | 0x110, 0x111, 0x112, 0x113, 0x123, 149 | 0x10B, 0x100, 0x101, 0x102, 0x124, 150 | 0x114, 0x115, 0x116, 0x117, 0x125, 151 | 0x11B, 0x11C, 0x11D, 0x11E, 0x126, 152 | # Armour 153 | 0x12A, 0x12B, 0x12C, 0x12D, 154 | 0x12E, 0x12F, 0x130, 0x131, 155 | 0x132, 0x133, 0x134, 0x135, 156 | 0x136, 0x137, 0x138, 0x139, 157 | 0x13A, 0x13B, 0x13C, 0x13D 158 | ] 159 | --------------------------------------------------------------------------------