UNPKG

bedrock-protocol

Version:
204 lines (181 loc) 6.46 kB
const { ClientStatus, Connection } = require('./connection') const Options = require('./options') const { serialize, isDebug } = require('./datatypes/util') const { KeyExchange } = require('./handshake/keyExchange') const Login = require('./handshake/login') const LoginVerify = require('./handshake/loginVerify') const debug = require('debug')('minecraft-protocol') class Player extends Connection { constructor (server, connection) { super() this.server = server this.features = server.features this.serializer = server.serializer this.deserializer = server.deserializer this.connection = connection this.options = server.options KeyExchange(this, server, server.options) Login(this, server, server.options) LoginVerify(this, server, server.options) this.startQueue() this.status = ClientStatus.Authenticating if (isDebug) { this.inLog = (...args) => debug('-> S', ...args) this.outLog = (...args) => debug('<- S', ...args) } this.batchHeader = this.server.batchHeader // Compression is server-wide this.compressionAlgorithm = this.server.compressionAlgorithm this.compressionLevel = this.server.compressionLevel this.compressionThreshold = this.server.compressionThreshold this.compressionHeader = this.server.compressionHeader this._sentNetworkSettings = false // 1.19.30+ } getUserData () { return this.userData } sendNetworkSettings () { this.write('network_settings', { compression_threshold: this.server.compressionThreshold, compression_algorithm: this.server.compressionAlgorithm, client_throttle: false, client_throttle_threshold: 0, client_throttle_scalar: 0 }) this._sentNetworkSettings = true this.compressionReady = true } handleClientProtocolVersion (clientVersion) { if (this.server.options.protocolVersion) { if (this.server.options.protocolVersion < clientVersion) { this.sendDisconnectStatus('failed_spawn') // client too new return false } } else if (clientVersion < Options.MIN_VERSION) { this.sendDisconnectStatus('failed_client') // client too old return false } return true } onLogin (packet) { const body = packet.data this.emit('loggingIn', body) const clientVer = body.params.protocol_version if (!this.handleClientProtocolVersion(clientVer)) { return } // Parse login data const tokens = body.params.tokens try { const skinChain = tokens.client const authChain = JSON.parse(tokens.identity) let chain if (authChain.Certificate) { // 1.21.90+ chain = JSON.parse(authChain.Certificate).chain } else if (authChain.chain) { chain = authChain.chain } else { throw new Error('Invalid login packet: missing chain or Certificate') } var { key, userData, skinData } = this.decodeLoginJWT(chain, skinChain) // eslint-disable-line } catch (e) { debug(this.address, e) this.disconnect('Server authentication error') return } this.emit('server.client_handshake', { key }) // internal so we start encryption this.userData = userData.extraData this.skinData = skinData this.profile = { name: userData.extraData?.displayName, uuid: userData.extraData?.identity, xuid: userData.extraData?.xuid || userData.extraData?.XUID } this.version = clientVer this.emit('login', { user: userData.extraData }) // emit events for user } /** * Disconnects a client before it has joined * @param {string} playStatus */ sendDisconnectStatus (playStatus) { if (this.status === ClientStatus.Disconnected) return this.write('play_status', { status: playStatus }) this.close('kick') } /** * Disconnects a client */ disconnect (reason = 'Server closed', hide = false) { if (this.status === ClientStatus.Disconnected) return this.write('disconnect', { hide_disconnect_screen: hide, message: reason, filtered_message: '' }) this.server.conLog('Kicked ', this.connection?.address, reason) setTimeout(() => this.close('kick'), 100) // Allow time for message to be recieved. } // After sending Server to Client Handshake, this handles the client's // Client to Server handshake response. This indicates successful encryption onHandshake () { // https://wiki.vg/Bedrock_Protocol#Play_Status this.write('play_status', { status: 'login_success' }) this.status = ClientStatus.Initializing this.emit('join') } close (reason) { if (this.status !== ClientStatus.Disconnected) { this.emit('close') // Emit close once if (!reason) this.inLog?.('Client closed connection', this.connection?.address) } this.q = [] this.q2 = [] clearInterval(this.loop) this.connection?.close() this.removeAllListeners() this.status = ClientStatus.Disconnected } readPacket (packet) { try { var des = this.server.deserializer.parsePacketBuffer(packet) // eslint-disable-line } catch (e) { this.disconnect('Server error') debug('Dropping packet from', this.connection.address, e) return } this.inLog?.(des.data.name, serialize(des.data.params)) switch (des.data.name) { // This is the first packet on 1.19.30 & above case 'request_network_settings': if (this.handleClientProtocolVersion(des.data.params.client_protocol)) { this.sendNetworkSettings() this.compressionLevel = this.server.compressionLevel } return // Below 1.19.30, this is the first packet. case 'login': this.onLogin(des) if (!this._sentNetworkSettings) this.sendNetworkSettings() return case 'client_to_server_handshake': // Emit the 'join' event this.onHandshake() break case 'set_local_player_as_initialized': this.status = ClientStatus.Initialized this.inLog?.('Server client spawned') // Emit the 'spawn' event this.emit('spawn') break default: if (this.status === ClientStatus.Disconnected || this.status === ClientStatus.Authenticating) { this.inLog?.('ignoring', des.data.name) return } } this.emit(des.data.name, des.data.params) this.emit('packet', des) } } module.exports = { Player }