client close handling, add spawn event (#48)

This commit is contained in:
extremeheat 2021-03-13 18:49:54 -05:00 committed by GitHub
commit bd97a8e1b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 102 additions and 49 deletions

View file

@ -1048,23 +1048,24 @@
}
]
],
"ItemStack": [
"container",
[
{
"name": "runtime_id",
"type": "zigzag32"
},
{
"name": "item",
"type": "Item"
}
]
],
"ItemStacks": [
"array",
{
"countType": "varint",
"type": [
"container",
[
{
"name": "runtime_id",
"type": "zigzag32"
},
{
"name": "item",
"type": "Item"
}
]
]
"type": "ItemStack"
}
],
"RecipeIngredient": [
@ -4600,13 +4601,9 @@
"name": "slot",
"type": "varint"
},
{
"name": "uniqueid",
"type": "zigzag32"
},
{
"name": "item",
"type": "Item"
"type": "ItemStack"
}
]
],

View file

@ -4,7 +4,7 @@
"description": "Parse and serialize Minecraft Bedrock Edition packets",
"main": "index.js",
"scripts": {
"build": "cd data/new && node compile.js",
"build": "cd tools && node compileProtocol.js",
"prepare": "npm run build",
"test": "mocha",
"pretest": "npm run lint",

View file

@ -1,4 +1,5 @@
const { Ber } = require('asn1')
const { ClientStatus } = require('../connection')
const JWT = require('jsonwebtoken')
const crypto = require('crypto')
const ecPem = require('ec-pem')
@ -82,6 +83,7 @@ function Encrypt (client, server, options) {
// It works! First encrypted packet :)
client.write('client_to_server_handshake', {})
this.emit('join')
client.status = ClientStatus.Initializing
}
client.on('server.client_handshake', startClientboundEncryption)

View file

@ -1,4 +1,4 @@
const { Connection } = require('./connection')
const { ClientStatus, Connection } = require('./connection')
const { createDeserializer, createSerializer } = require('./transforms/serializer')
const { RakClient } = require('./Rak')
const { serialize } = require('./datatypes/util')
@ -32,10 +32,12 @@ class Client extends Connection {
auth.authenticateDeviceCode(this, options)
}
this.startGameData = {}
this.on('session', this.connect)
this.startQueue()
this.inLog = (...args) => console.info('C ->', ...args)
this.outLog = (...args) => console.info('C <-', ...args)
this.inLog = (...args) => debug('C ->', ...args)
this.outLog = (...args) => debug('C <-', ...args)
}
validateOptions () {
@ -62,11 +64,13 @@ class Client extends Connection {
this.connection = new RakClient({ useWorkers: true, hostname, port })
this.connection.onConnected = () => this.sendLogin()
this.connection.onCloseConnection = () => this._close()
this.connection.onEncapsulated = this.onEncapsulated
this.connection.connect()
}
sendLogin () {
this.status = ClientStatus.Authenticating
this.createClientChain()
const chain = [
@ -96,8 +100,25 @@ class Client extends Connection {
process.exit(1) // TODO: handle
}
onPlayStatus(statusPacket) {
if (this.status == ClientStatus.Initializing && this.options.autoInitPlayer === true) {
if (statusPacket.status === 'player_spawn') {
this.status = ClientStatus.Initialized
this.write('set_local_player_as_initialized', { runtime_entity_id: this.startGameData.runtime_entity_id })
this.emit('spawn')
}
}
}
_close() {
this.q = []
this.q2 = []
}
close () {
console.warn('Close not implemented!!')
this._close()
this.connection.close()
console.log('Closed!')
}
tryRencode (name, params, actual) {
@ -147,15 +168,12 @@ class Client extends Connection {
case 'disconnect': // Client kicked
this.onDisconnectRequest(des.data.params)
break
// case 'crafting_data':
// fs.writeFileSync('crafting.json', JSON.stringify(des.data.params, (k, v) => typeof v == 'bigint' ? v.toString() : v))
// break
// case 'start_game':
// fs.writeFileSync('start_game.json', JSON.stringify(des.data.params, (k, v) => typeof v == 'bigint' ? v.toString() : v))
// break
// case 'level_chunk':
// // fs.writeFileSync(`./chunks/chunk-${chunks++}.txt`, packet.toString('hex'))
// break
case 'start_game':
this.startGameData = pakData.params
break
case 'play_status':
this.onPlayStatus(pakData.params)
break
default:
// console.log('Sending to listeners')
}

View file

@ -7,7 +7,16 @@ 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
@ -112,6 +121,7 @@ class Connection extends EventEmitter {
// TODO: Rename this to sendEncapsulated
sendMCPE (buffer, immediate) {
if (this.connection.connected === false) return
this.connection.sendReliable(buffer, immediate)
}
@ -151,4 +161,4 @@ class Connection extends EventEmitter {
}
}
module.exports = { Connection }
module.exports = { ClientStatus, Connection }

View file

@ -5,11 +5,16 @@ const CURRENT_VERSION = '1.16.201'
const defaultOptions = {
// https://minecraft.gamepedia.com/Protocol_version#Bedrock_Edition_2
version: CURRENT_VERSION
version: CURRENT_VERSION,
// client: If we should send SetPlayerInitialized to the server after getting play_status spawn.
// if this is disabled, no 'spawn' event will be emitted, you should manually set
// client.status to ClientStatus.Initialized after sending the init packet.
autoInitPlayer: true
}
const Versions = {
'1.16.210': 428,
// TODO
// '1.16.210': 428,
'1.16.201': 422
}

View file

@ -14,6 +14,7 @@ try {
class RakNativeClient extends EventEmitter {
constructor (options) {
super()
this.connected = false
this.onConnected = () => { }
this.onCloseConnection = () => { }
this.onEncapsulated = () => { }
@ -23,8 +24,14 @@ class RakNativeClient extends EventEmitter {
this.onEncapsulated(buffer, address)
})
this.raknet.on('connected', () => {
this.connected = true
this.onConnected()
})
this.raknet.on('disconnected', ({ reason }) => {
this.connected = false
this.onCloseConnection(reason)
})
}
async ping () {
@ -42,7 +49,15 @@ class RakNativeClient extends EventEmitter {
this.raknet.connect()
}
close() {
this.connected = false
setTimeout(() => {
this.raknet.close()
}, 40)
}
sendReliable (buffer, immediate) {
if (!this.connected) return
const priority = immediate ? PacketPriority.IMMEDIATE_PRIORITY : PacketPriority.MEDIUM_PRIORITY
return this.raknet.send(buffer, priority, PacketReliability.RELIABLE_ORDERED, 0)
}
@ -83,6 +98,10 @@ class RakNativeServer extends EventEmitter {
listen () {
this.raknet.listen()
}
close() {
this.raknet.close()
}
}
class RakJsClient extends EventEmitter {

View file

@ -106,6 +106,7 @@ class RelayPlayer extends Player {
}
this.flushUpQueue() // Send queued packets
this.downInLog('Recv packet', packet)
// TODO: If we fail to parse a packet, proxy it raw and log an error
const des = this.server.deserializer.parsePacketBuffer(packet)
if (debugging) { // some packet encode/decode testing stuff
@ -150,7 +151,8 @@ class Relay extends Server {
const client = new Client({
hostname: this.options.destination.hostname,
port: this.options.destination.port,
encrypt: this.options.encrypt
encrypt: this.options.encrypt,
autoInitPlayer: false
})
client.outLog = ds.upOutLog
client.inLog = ds.upInLog

View file

@ -1,4 +1,4 @@
const { Connection } = require('./connection')
const { ClientStatus, Connection } = require('./connection')
const fs = require('fs')
const Options = require('./options')
@ -6,12 +6,6 @@ const { Encrypt } = require('./auth/encryption')
const Login = require('./auth/login')
const LoginVerify = require('./auth/loginVerify')
const ClientStatus = {
Authenticating: 0,
Initializing: 1,
Initialized: 2
}
class Player extends Connection {
constructor (server, connection) {
super()

View file

@ -7,6 +7,11 @@
*/
const fs = require('fs')
const { ProtoDefCompiler } = require('protodef').Compiler
const { join } = require('path')
function getJSON (path) {
return JSON.parse(fs.readFileSync(path, 'utf-8'))
}
// Parse the YML files and turn to JSON
function genProtoSchema () {
@ -37,15 +42,15 @@ function genProtoSchema () {
const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}`
fs.writeFileSync('./packet_map.yml', t)
compile('./proto.yml', 'protocol.json')
compile('./proto.yml', 'proto.json')
return version
}
// Compile the ProtoDef JSON into JS
function createProtocol (version) {
const compiler = new ProtoDefCompiler()
const protocol = require(`../${version}/protocol.json`).types
compiler.addTypes(require('../../src/datatypes/compiler-minecraft'))
const protocol = getJSON(`../${version}/protocol.json`).types
compiler.addTypes(require('../src/datatypes/compiler-minecraft'))
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
compiler.addTypesToCompile(protocol)
@ -57,11 +62,12 @@ function createProtocol (version) {
return compiledProto
}
function main () {
function main (ver = 'latest') {
process.chdir(join(__dirname, '/../data/', ver))
const version = genProtoSchema()
fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: require('./protocol.json') }, null, 2))
fs.unlinkSync('./protocol.json') // remove temp file
fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: getJSON('./proto.json') }, null, 2))
fs.unlinkSync('./proto.json') // remove temp file
fs.unlinkSync('./packet_map.yml') // remove temp file
console.log('Generating JS...')