bedrock-protocol/src/serverPlayer.js
2021-06-23 00:10:58 -04:00

158 lines
4.7 KiB
JavaScript

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 fs = require('fs')
const debug = require('debug')('minecraft-protocol')
class Player extends Connection {
constructor (server, connection) {
super()
this.server = server
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)
}
}
getUserData () {
return this.userData
}
onLogin (packet) {
const body = packet.data
this.emit('loggingIn', body)
const clientVer = body.protocol_version
if (this.server.options.protocolVersion) {
if (this.server.options.protocolVersion < clientVer) {
this.sendDisconnectStatus('failed_spawn')
return
}
} else if (clientVer < Options.MIN_VERSION) {
this.sendDisconnectStatus('failed_client')
return
}
// Parse login data
const tokens = body.params.tokens
const authChain = JSON.parse(tokens.identity)
const skinChain = tokens.client
try {
var { key, userData, skinData } = this.decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line
} catch (e) {
console.error(e)
console.debug(authChain.chain, skinChain)
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
}
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
})
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')
fs.writeFile(`packetdump_${this.connection.address}_${Date.now()}.bin`, packet)
return
}
this.inLog?.(des.data.name, serialize(des.data.params).slice(0, 200))
switch (des.data.name) {
case 'login':
this.onLogin(des)
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)
}
}
module.exports = { Player }