pocket-minecraft-protocol
Version:
Parse and serialize Minecraft Bedrock Edition packets
165 lines (144 loc) • 4.52 kB
JavaScript
const BinaryStream = require('@jsprismarine/jsbinaryutils').default
const BatchPacket = require('./datatypes/BatchPacket')
const cipher = require('./transforms/encryption')
const { EventEmitter } = require('events')
const Versions = require('./options')
const debug = require('debug')('minecraft-protocol')
const SKIP_BATCH = ['level_chunk', 'client_cache_blob_status', 'client_cache_miss_response']
const ClientStatus = {
Disconnected: 0,
Authenticating: 1, // Handshaking
Initializing: 2, // Authed, need to spawn
Initialized: 3 // play_status spawn sent by server, client responded with SetPlayerInit packet
}
class Connection extends EventEmitter {
state = ClientStatus.Disconnected
versionLessThan (version) {
if (typeof version === 'string') {
return Versions[version] < this.options.version
} else {
return version < this.options.version
}
}
versionGreaterThan (version) {
if (typeof version === 'string') {
return Versions[version] > this.options.version
} else {
return version > this.options.version
}
}
startEncryption (iv) {
this.encryptionEnabled = true
this.inLog('Started encryption', this.sharedSecret, iv)
this.decrypt = cipher.createDecryptor(this, iv)
this.encrypt = cipher.createEncryptor(this, iv)
this.q2 = []
}
write (name, params) {
this.outLog('sending', name, params)
const batch = new BatchPacket()
const packet = this.serializer.createPacketBuffer({ name, params })
batch.addEncodedPacket(packet)
if (this.encryptionEnabled) {
this.sendEncryptedBatch(batch)
} else {
this.sendDecryptedBatch(batch)
}
}
queue (name, params) {
this.outLog('Q <- ', name, params)
const packet = this.serializer.createPacketBuffer({ name, params })
if (SKIP_BATCH.includes(name)) {
// Skip queue, send ASAP
this.sendBuffer(packet)
return
}
this.q.push(packet)
this.q2.push(name)
}
startQueue () {
this.q = []
this.loop = setInterval(() => {
if (this.q.length) {
// TODO: can we just build Batch before the queue loop?
const batch = new BatchPacket()
this.outLog('<- BATCH', this.q2)
const sending = []
for (let i = 0; i < this.q.length; i++) {
const packet = this.q.shift()
sending.push(this.q2.shift())
batch.addEncodedPacket(packet)
}
// this.outLog('~~ Sending', sending)
if (this.encryptionEnabled) {
this.sendEncryptedBatch(batch)
} else {
this.sendDecryptedBatch(batch)
}
}
}, 20)
}
/**
* Sends a MCPE packet buffer
*/
sendBuffer (buffer, immediate = false) {
if (immediate) {
const batch = new BatchPacket()
batch.addEncodedPacket(buffer)
if (this.encryptionEnabled) {
this.sendEncryptedBatch(batch)
} else {
this.sendDecryptedBatch(batch)
}
} else {
this.q.push(buffer)
this.q2.push('rawBuffer')
}
}
sendDecryptedBatch (batch) {
const buf = batch.encode()
// send to raknet
this.sendMCPE(buf, true)
}
sendEncryptedBatch (batch) {
const buf = batch.stream.getBuffer()
debug('Sending encrypted batch', batch)
this.encrypt(buf)
}
// TODO: Rename this to sendEncapsulated
sendMCPE (buffer, immediate) {
if (this.connection.connected === false) return
this.connection.sendReliable(buffer, immediate)
}
// These are callbacks called from encryption.js
onEncryptedPacket = (buf) => {
this.outLog('Enc buf', buf)
const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header
this.outLog('Sending wrapped encrypted batch', packet)
this.sendMCPE(packet)
}
onDecryptedPacket = (buf) => {
const stream = new BinaryStream(buf)
const packets = BatchPacket.getPackets(stream)
for (const packet of packets) {
this.readPacket(packet)
}
}
handle (buffer) { // handle encapsulated
if (buffer[0] === 0xfe) { // wrapper
if (this.encryptionEnabled) {
this.decrypt(buffer.slice(1))
} else {
const stream = new BinaryStream(buffer)
const batch = new BatchPacket(stream)
batch.decode()
const packets = batch.getPackets()
this.inLog('Reading ', packets.length, 'packets')
for (const packet of packets) {
this.readPacket(packet)
}
}
}
}
}
module.exports = { ClientStatus, Connection }