minecraft-protocol
Version:
Parse and serialize minecraft packets, plus authentication and encryption.
185 lines (156 loc) • 5.4 kB
JavaScript
const nbt = require('prismarine-nbt')
const UUID = require('uuid-1345')
const zlib = require('zlib')
const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint
const [readLpVec3, writeLpVec3, sizeOfLpVec3] = require('./lpVec3')
module.exports = {
varlong: [readVarLong, writeVarLong, sizeOfVarLong],
UUID: [readUUID, writeUUID, 16],
compressedNbt: [readCompressedNbt, writeCompressedNbt, sizeOfCompressedNbt],
restBuffer: [readRestBuffer, writeRestBuffer, sizeOfRestBuffer],
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
topBitSetTerminatedArray: [readTopBitSetTerminatedArray, writeTopBitSetTerminatedArray, sizeOfTopBitSetTerminatedArray],
lpVec3: [readLpVec3, writeLpVec3, sizeOfLpVec3]
}
const PartialReadError = require('protodef').utils.PartialReadError
function readVarLong (buffer, offset) {
return readVarInt(buffer, offset)
}
function writeVarLong (value, buffer, offset) {
return writeVarInt(value, buffer, offset)
}
function sizeOfVarLong (value) {
return sizeOfVarInt(value)
}
function readUUID (buffer, offset) {
if (offset + 16 > buffer.length) { throw new PartialReadError() }
return {
value: UUID.stringify(buffer.slice(offset, 16 + offset)),
size: 16
}
}
function writeUUID (value, buffer, offset) {
const buf = value.length === 32 ? Buffer.from(value, 'hex') : UUID.parse(value)
buf.copy(buffer, offset)
return offset + 16
}
function sizeOfNbt (value, { tagType } = { tagType: 'nbt' }) {
return nbt.proto.sizeOf(value, tagType)
}
// Length-prefixed compressed NBT, see differences: http://wiki.vg/index.php?title=Slot_Data&diff=6056&oldid=4753
function readCompressedNbt (buffer, offset) {
if (offset + 2 > buffer.length) { throw new PartialReadError() }
const length = buffer.readInt16BE(offset)
if (length === -1) return { size: 2 }
if (offset + 2 + length > buffer.length) { throw new PartialReadError() }
const compressedNbt = buffer.slice(offset + 2, offset + 2 + length)
const nbtBuffer = zlib.gunzipSync(compressedNbt) // TODO: async
const results = nbt.proto.read(nbtBuffer, 0, 'nbt')
return {
size: length + 2,
value: results.value
}
}
function writeCompressedNbt (value, buffer, offset) {
if (value === undefined) {
buffer.writeInt16BE(-1, offset)
return offset + 2
}
const nbtBuffer = Buffer.alloc(sizeOfNbt(value))
nbt.proto.write(value, nbtBuffer, 0, 'nbt')
const compressedNbt = zlib.gzipSync(nbtBuffer) // TODO: async
compressedNbt.writeUInt8(0, 9) // clear the OS field to match MC
buffer.writeInt16BE(compressedNbt.length, offset)
compressedNbt.copy(buffer, offset + 2)
return offset + 2 + compressedNbt.length
}
function sizeOfCompressedNbt (value) {
if (value === undefined) { return 2 }
const nbtBuffer = Buffer.alloc(sizeOfNbt(value, { tagType: 'nbt' }))
nbt.proto.write(value, nbtBuffer, 0, 'nbt')
const compressedNbt = zlib.gzipSync(nbtBuffer) // TODO: async
return 2 + compressedNbt.length
}
function readRestBuffer (buffer, offset) {
return {
value: buffer.slice(offset),
size: buffer.length - offset
}
}
function writeRestBuffer (value, buffer, offset) {
value.copy(buffer, offset)
return offset + value.length
}
function sizeOfRestBuffer (value) {
return value.length
}
function readEntityMetadata (buffer, offset, { type, endVal }) {
let cursor = offset
const metadata = []
let item
while (true) {
if (offset + 1 > buffer.length) { throw new PartialReadError() }
item = buffer.readUInt8(cursor)
if (item === endVal) {
return {
value: metadata,
size: cursor + 1 - offset
}
}
const results = this.read(buffer, cursor, type, {})
metadata.push(results.value)
cursor += results.size
}
}
function writeEntityMetadata (value, buffer, offset, { type, endVal }) {
const self = this
value.forEach(function (item) {
offset = self.write(item, buffer, offset, type, {})
})
buffer.writeUInt8(endVal, offset)
return offset + 1
}
function sizeOfEntityMetadata (value, { type }) {
let size = 1
for (let i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {})
}
return size
}
function readTopBitSetTerminatedArray (buffer, offset, { type }) {
let cursor = offset
const values = []
let item
while (true) {
if (offset + 1 > buffer.length) { throw new PartialReadError() }
item = buffer.readUInt8(cursor)
buffer[cursor] = buffer[cursor] & 127 // removes top bit
const results = this.read(buffer, cursor, type, {})
values.push(results.value)
cursor += results.size
if ((item & 128) === 0) { // check if top bit is set, if not last value
return {
value: values,
size: cursor - offset
}
}
}
}
function writeTopBitSetTerminatedArray (value, buffer, offset, { type }) {
const self = this
let prevOffset = offset
value.forEach(function (item, i) {
prevOffset = offset
offset = self.write(item, buffer, offset, type, {})
buffer[prevOffset] = i !== value.length - 1 ? (buffer[prevOffset] | 128) : buffer[prevOffset] // set top bit for all values but last
})
return offset
}
function sizeOfTopBitSetTerminatedArray (value, { type }) {
let size = 0
for (let i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {})
}
return size
}