add new packets and working proxy
This commit is contained in:
parent
86dcbc1f49
commit
9e1500ec77
9 changed files with 430 additions and 9 deletions
|
|
@ -150,6 +150,11 @@ mcpe_packet:
|
|||
0x9a: position_tracking_db_request
|
||||
0x99: position_tracking_db_broadcast
|
||||
0x9c: packet_violation_warning
|
||||
0x9d: motion_prediction_hints
|
||||
0x9e: animate_entity
|
||||
0x9f: camera_shake
|
||||
0xa0: player_fog
|
||||
0xa1: correct_player_move_prediction
|
||||
0xa2: item_component
|
||||
0xa3: filter_text_packet
|
||||
|
||||
|
|
@ -303,5 +308,10 @@ mcpe_packet:
|
|||
if position_tracking_db_request: packet_position_tracking_db_request
|
||||
if position_tracking_db_broadcast: packet_position_tracking_db_broadcast
|
||||
if packet_violation_warning: packet_packet_violation_warning
|
||||
if motion_prediction_hints: packet_motion_prediction_hints
|
||||
if animate_entity: packet_animate_entity
|
||||
if camera_shake: packet_camera_shake
|
||||
if player_fog: packet_player_fog
|
||||
if correct_player_move_prediction: packet_correct_player_move_prediction
|
||||
if item_component: packet_item_component
|
||||
if filter_text_packet: packet_filter_text_packet
|
||||
|
|
|
|||
|
|
@ -2204,6 +2204,87 @@ packet_packet_violation_warning:
|
|||
# ViolationContext holds a description on the violation of the packet.
|
||||
reason: string
|
||||
|
||||
|
||||
# MotionPredictionHints is sent by the server to the client. There is a predictive movement component for
|
||||
# entities. This packet fills the "history" of that component and entity movement is computed based on the
|
||||
# points. Vanilla sends this packet instead of the SetActorMotion packet when 'spatial optimisations' are
|
||||
# enabled.
|
||||
packet_motion_prediction_hints:
|
||||
!id: 0x9d
|
||||
!bound: client
|
||||
# EntityRuntimeID is the runtime ID of the entity whose velocity is sent to the client.
|
||||
entity_runtime_id: varint64
|
||||
# Velocity is the server-calculated velocity of the entity at the point of sending the packet.
|
||||
velocity: vec3f
|
||||
# OnGround specifies if the server currently thinks the entity is on the ground.
|
||||
on_ground: bool
|
||||
|
||||
|
||||
# AnimateEntity is sent by the server to animate an entity client-side. It may be used to play a single
|
||||
# animation, or to activate a controller which can start a sequence of animations based on different
|
||||
# conditions specified in an animation controller.
|
||||
# Much of the documentation of this packet can be found at
|
||||
# https://minecraft.gamepedia.com/Bedrock_Edition_beta_animation_documentation.
|
||||
packet_animate_entity:
|
||||
!id: 0x9e
|
||||
!bound: client
|
||||
# Animation is the name of a single animation to start playing.
|
||||
animation: string
|
||||
# NextState is the first state to start with. These states are declared in animation controllers (which,
|
||||
# in themselves, are animations too). These states in turn may have animations and transitions to move to
|
||||
# a next state.
|
||||
next_state: string
|
||||
# StopCondition is a MoLang expression that specifies when the animation should be stopped.
|
||||
stop_condition: string
|
||||
# Controller is the animation controller that is used to manage animations. These controllers decide when
|
||||
# to play which animation.
|
||||
controller: string
|
||||
# BlendOutTime does not currently seem to be used.
|
||||
blend_out_time: lf32
|
||||
# EntityRuntimeIDs is list of runtime IDs of entities that the animation should be applied to.
|
||||
runtime_entity_ids: varint64[]varint
|
||||
|
||||
# CameraShake is sent by the server to make the camera shake client-side. This feature was added for map-
|
||||
# making partners.
|
||||
packet_camera_shake:
|
||||
!id: 0x9f
|
||||
!bound: client
|
||||
# Intensity is the intensity of the shaking. The client limits this value to 4, so anything higher may
|
||||
# not work.
|
||||
intensity: lf32
|
||||
# Duration is the number of seconds the camera will shake for.
|
||||
duration: lf32
|
||||
# Type is the type of shake, and is one of the constants listed above. The different type affects how
|
||||
# the shake looks in game.
|
||||
type: u8
|
||||
|
||||
# PlayerFog is sent by the server to render the different fogs in the Stack. The types of fog are controlled
|
||||
# by resource packs to change how they are rendered, and the ability to create custom fog.
|
||||
packet_player_fog:
|
||||
!id: 0xa0
|
||||
!bound: client
|
||||
# Stack is a list of fog identifiers to be sent to the client. Examples of fog identifiers are
|
||||
# "minecraft:fog_ocean" and "minecraft:fog_hell".
|
||||
stack: string[]varint
|
||||
|
||||
|
||||
# CorrectPlayerMovePrediction is sent by the server if and only if StartGame.ServerAuthoritativeMovementMode
|
||||
# is set to AuthoritativeMovementModeServerWithRewind. The packet is used to correct movement at a specific
|
||||
# point in time.
|
||||
packet_correct_player_move_prediction:
|
||||
!id: 0xa1
|
||||
!bound: client
|
||||
# Position is the position that the player is supposed to be at at the tick written in the field below.
|
||||
# The client will change its current position based on movement after that tick starting from the
|
||||
# Position.
|
||||
position: vec3f
|
||||
# Delta is the change in position compared to what the client sent as its position at that specific tick.
|
||||
delta: vec3f
|
||||
# OnGround specifies if the player was on the ground at the time of the tick below.
|
||||
on_ground: bool
|
||||
# Tick is the tick of the movement which was corrected by this packet.
|
||||
tick: varint64
|
||||
|
||||
# ItemComponent is sent by the server to attach client-side components to a custom item.
|
||||
packet_item_component:
|
||||
!id: 0xa2
|
||||
|
|
@ -2221,3 +2302,4 @@ packet_filter_text_packet:
|
|||
text: string
|
||||
# FromServer indicates if the packet was sent by the server or not.
|
||||
from_server: bool
|
||||
|
||||
|
|
|
|||
|
|
@ -2694,6 +2694,11 @@
|
|||
"153": "position_tracking_db_broadcast",
|
||||
"154": "position_tracking_db_request",
|
||||
"156": "packet_violation_warning",
|
||||
"157": "motion_prediction_hints",
|
||||
"158": "animate_entity",
|
||||
"159": "camera_shake",
|
||||
"160": "player_fog",
|
||||
"161": "correct_player_move_prediction",
|
||||
"162": "item_component",
|
||||
"163": "filter_text_packet"
|
||||
}
|
||||
|
|
@ -2856,6 +2861,11 @@
|
|||
"position_tracking_db_request": "packet_position_tracking_db_request",
|
||||
"position_tracking_db_broadcast": "packet_position_tracking_db_broadcast",
|
||||
"packet_violation_warning": "packet_packet_violation_warning",
|
||||
"motion_prediction_hints": "packet_motion_prediction_hints",
|
||||
"animate_entity": "packet_animate_entity",
|
||||
"camera_shake": "packet_camera_shake",
|
||||
"player_fog": "packet_player_fog",
|
||||
"correct_player_move_prediction": "packet_correct_player_move_prediction",
|
||||
"item_component": "packet_item_component",
|
||||
"filter_text_packet": "packet_filter_text_packet"
|
||||
},
|
||||
|
|
@ -6743,6 +6753,111 @@
|
|||
}
|
||||
]
|
||||
],
|
||||
"packet_motion_prediction_hints": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "entity_runtime_id",
|
||||
"type": "varint64"
|
||||
},
|
||||
{
|
||||
"name": "velocity",
|
||||
"type": "vec3f"
|
||||
},
|
||||
{
|
||||
"name": "on_ground",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
],
|
||||
"packet_animate_entity": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "animation",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "next_state",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "stop_condition",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "controller",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "blend_out_time",
|
||||
"type": "lf32"
|
||||
},
|
||||
{
|
||||
"name": "runtime_entity_ids",
|
||||
"type": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"type": "varint64"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"packet_camera_shake": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "intensity",
|
||||
"type": "lf32"
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "lf32"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"type": "u8"
|
||||
}
|
||||
]
|
||||
],
|
||||
"packet_player_fog": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "stack",
|
||||
"type": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"packet_correct_player_move_prediction": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "position",
|
||||
"type": "vec3f"
|
||||
},
|
||||
{
|
||||
"name": "delta",
|
||||
"type": "vec3f"
|
||||
},
|
||||
{
|
||||
"name": "on_ground",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "tick",
|
||||
"type": "varint64"
|
||||
}
|
||||
]
|
||||
],
|
||||
"packet_item_component": [
|
||||
"container",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ class Client extends Connection {
|
|||
// console.log('packet', packet)
|
||||
const des = this.deserializer.parsePacketBuffer(packet)
|
||||
const pakData = { name: des.data.name, params: des.data.params }
|
||||
this.inLog('-> C', pakData.name, serialize(pakData.params).slice(0, 100))
|
||||
this.inLog('-> C', pakData.name/*, serialize(pakData.params).slice(0, 100)*/)
|
||||
|
||||
// No idea what this exotic 0xA0 packet is, it's not implemented anywhere
|
||||
// and seems empty. Possible gibberish from the raknet impl
|
||||
|
|
@ -149,7 +149,7 @@ class Client extends Connection {
|
|||
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'))
|
||||
// fs.writeFileSync(`./chunks/chunk-${chunks++}.txt`, packet.toString('hex'))
|
||||
break
|
||||
default:
|
||||
// console.log('Sending to listeners')
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class Connection extends EventEmitter {
|
|||
// console.log('Need to encode', name, params)
|
||||
var s = this.connect ? 'C' : 'S'
|
||||
if (this.downQ) s += 'P'
|
||||
this.outLog('<- ' + s, name)
|
||||
this.outLog('NB <- ' + s, name,params)
|
||||
const batch = new BatchPacket()
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
// console.log('Sending buf', packet.toString('hex').)
|
||||
|
|
@ -33,8 +33,13 @@ class Connection extends EventEmitter {
|
|||
}
|
||||
|
||||
queue(name, params) {
|
||||
this.outLog('Q <- ', name)
|
||||
this.outLog('Q <- ', name, params)
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
if (name == 'level_chunk') {
|
||||
// Skip queue
|
||||
this.sendMCPE(packet)
|
||||
return
|
||||
}
|
||||
this.q.push(packet)
|
||||
this.q2.push(name)
|
||||
}
|
||||
|
|
@ -48,16 +53,19 @@ class Connection extends EventEmitter {
|
|||
this.outLog('<- BATCH', this.q2)
|
||||
// For now, we're over conservative so send max 3 packets
|
||||
// per batch and hold the rest for the next tick
|
||||
for (let i = 0; /*i < 10 &&*/ i < this.q.length; i++) {
|
||||
const sending = []
|
||||
for (let i = 0; i < 3 && i < this.q.length; i++) {
|
||||
const packet = this.q.shift()
|
||||
sending.push(this.q2.shift())
|
||||
batch.addEncodedPacket(packet)
|
||||
}
|
||||
// console.warn('~~ Sending', sending)
|
||||
if (this.encryptionEnabled) {
|
||||
this.sendEncryptedBatch(batch)
|
||||
} else {
|
||||
this.sendDecryptedBatch(batch)
|
||||
}
|
||||
this.q2 = []
|
||||
// this.q2 = []
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
|
@ -162,5 +170,7 @@ class Connection extends EventEmitter {
|
|||
// console.log('[client] handled incoming ', buffer)
|
||||
}
|
||||
}
|
||||
|
||||
function serialize(obj = {}, fmt) {
|
||||
return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt)
|
||||
}
|
||||
module.exports = { Connection }
|
||||
|
|
@ -57,7 +57,8 @@ class RakNativeServer extends EventEmitter {
|
|||
this.onEncapsulated = () => {}
|
||||
this.raknet = new Server(options.hostname, options.port, {
|
||||
maxConnections: options.maxConnections || 3,
|
||||
minecraft: { message: new McPingMessage().toString() }
|
||||
minecraft: { },
|
||||
message: new McPingMessage().toBuffer()
|
||||
})
|
||||
|
||||
this.raknet.on('openConnection', (client) => {
|
||||
|
|
|
|||
202
src/relay.js
Normal file
202
src/relay.js
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
process.env.DEBUG = 'minecraft-protocol raknet'
|
||||
const { Client } = require("./client")
|
||||
const { Server } = require("./server")
|
||||
const { Player } = require("./serverPlayer")
|
||||
const debug = require('debug')('minecraft-protocol relay')
|
||||
|
||||
/** @typedef {{ hostname: string, port: number, auth: 'client' | 'server' | null, destination?: { hostname: string, port: number } }} Options */
|
||||
|
||||
class RelayPlayer extends Player {
|
||||
constructor(server, conn) {
|
||||
super(server, conn)
|
||||
this.server = server
|
||||
this.conn = conn
|
||||
|
||||
this.startRelaying = false
|
||||
this.once('join', () => {
|
||||
this.write('client_cache_status', {enabled:false}) // disable this asap on join
|
||||
|
||||
this.flushDownQueue()
|
||||
this.startRelaying = true
|
||||
})
|
||||
this.downQ = []
|
||||
this.upQ = []
|
||||
this.upInLog = (...msg) => console.info('** Backend -> Proxy', ...msg)
|
||||
this.upOutLog = (...msg) => console.info('** Proxy -> Backend', ...msg)
|
||||
this.downInLog = (...msg) => console.info('** Client -> Proxy', ...msg)
|
||||
this.downOutLog = (...msg) => console.info('** Proxy -> Client', ...msg)
|
||||
|
||||
this.outLog = this.downOutLog
|
||||
this.inLog = this.downInLog
|
||||
}
|
||||
|
||||
// Called when we get a packet from backend server (Backend -> PROXY -> Client)
|
||||
readUpstream(packet) {
|
||||
if (!this.startRelaying) {
|
||||
console.warn('The downstream client is not ready yet !!')
|
||||
this.downQ.push(packet)
|
||||
return
|
||||
}
|
||||
this.upInLog('Recv packet', packet)
|
||||
const des = this.server.deserializer.parsePacketBuffer(packet)
|
||||
const name = des.data.name
|
||||
const params = des.data.params
|
||||
this.upInLog('~~ Bounce B->C', name, serialize(params).slice(0, 100))
|
||||
this.upInLog('~~ ', des.buffer)
|
||||
if (name == 'play_status' && params.status == 'login_success') return
|
||||
|
||||
if (name == 'level_chunk') { //send chunk directly
|
||||
this.upInLog('Would send chunk', params)
|
||||
this.sendBuffer(packet)
|
||||
return
|
||||
} else this.upInLog('?',name)
|
||||
|
||||
// if (name == 'network_chunk_publisher_update') return
|
||||
// if (name == 'crafting_data' || name == 'level_chunk') return // Alex breaks
|
||||
this.queue(name, params)
|
||||
// this.sendBuffer(packet)
|
||||
}
|
||||
|
||||
flushDownQueue() {
|
||||
for (const packet of this.downQ) {
|
||||
const des = this.server.deserializer.parsePacketBuffer(packet)
|
||||
this.write(des.data.name, des.data.params)
|
||||
}
|
||||
this.downQ = []
|
||||
}
|
||||
|
||||
flushUpQueue() {
|
||||
for (var e of this.upQ) { // Send the queue
|
||||
const des = this.server.deserializer.parsePacketBuffer(e)
|
||||
if (des.data.name == 'client_cache_status') { // already disabled on join
|
||||
// this.upstream.write('client_cache_status', {enabled:false})
|
||||
} else {
|
||||
this.upstream.write(des.data.name, des.data.params)
|
||||
}
|
||||
}
|
||||
this.upQ = []
|
||||
}
|
||||
|
||||
// Called when the server gets a packet from the downstream player (Client -> PROXY -> Backend)
|
||||
readPacket(packet) {
|
||||
if (this.startRelaying) { // The DS client conn is established & we got a packet to send to US server
|
||||
if (!this.upstream) { // Upstream is still connecting/handshaking
|
||||
debug('Got downstream connected packet but upstream is not connected yet, added to q', this.queue.length)
|
||||
this.upQ.push(packet) // Put into a queue
|
||||
return
|
||||
}
|
||||
this.flushUpQueue() // Send queued packets
|
||||
this.downInLog('Recv packet', packet)
|
||||
const des = this.server.deserializer.parsePacketBuffer(packet)
|
||||
switch (des.data.name) {
|
||||
case 'client_cache_status':
|
||||
this.upstream.queue('client_cache_status', {enabled:false})
|
||||
break
|
||||
// case 'request_chunk_radius':
|
||||
// this.upstream.queue('request_chunk_radius', {chunk_radius: 1})
|
||||
// break
|
||||
default:
|
||||
// Emit the packet as-is back to the upstream server
|
||||
this.upstream.queue(des.data.name, des.data.params)
|
||||
}
|
||||
} else {
|
||||
super.readPacket(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Relay extends Server {
|
||||
/**
|
||||
* Creates a new non-transparent proxy connection to a destination server
|
||||
* @param {Options} options
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options)
|
||||
this.RelayPlayer = options.relayPlayer || RelayPlayer
|
||||
this.forceSingle = true
|
||||
this.upstreams = new Map()
|
||||
}
|
||||
|
||||
openUpstreamConnection(ds, clientAddr) {
|
||||
const client = new Client({
|
||||
hostname: this.options.destination.hostname,
|
||||
port: this.options.destination.port,
|
||||
encrypt: this.options.encrypt
|
||||
})
|
||||
client.outLog = ds.upOutLog
|
||||
client.inLog = ds.upInLog
|
||||
// console.log('Set upstream logs', client.outLog, client.inLog)
|
||||
client.once('join', () => { // Intercept once handshaking done
|
||||
ds.upstream = client
|
||||
ds.flushUpQueue()
|
||||
console.log('UPSTREAM HAS JOINED')
|
||||
client.readPacket = (packet) => ds.readUpstream(packet)
|
||||
})
|
||||
this.upstreams.set(clientAddr.hash, client)
|
||||
}
|
||||
|
||||
closeUpstreamConnection(clientAddr) {
|
||||
const up = this.upstreams.get(clientAddr.hash)
|
||||
if (!up) throw Error(`unable to close non-existant connection ${clientAddr.hash}`)
|
||||
up.close()
|
||||
this.upstreams.delete(clientAddr.hash)
|
||||
debug('relay closed connection', clientAddr)
|
||||
}
|
||||
|
||||
onOpenConnection = (conn) => {
|
||||
debug('new connection', conn)
|
||||
if (this.forceSingle && this.clientCount > 0) {
|
||||
debug('dropping connection as single client relay', conn)
|
||||
conn.close()
|
||||
} else {
|
||||
const player = new this.RelayPlayer(this, conn)
|
||||
console.log('NEW CONNECTION', conn.address)
|
||||
this.clients[conn.address] = player
|
||||
this.emit('connect', { client: player })
|
||||
this.openUpstreamConnection(player, conn.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log = () => {}
|
||||
|
||||
|
||||
function serialize(obj = {}, fmt) {
|
||||
return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt)
|
||||
}
|
||||
|
||||
function createRelay() {
|
||||
console.log('Creating relay')
|
||||
/**
|
||||
* Example to create a non-transparent proxy (or 'Relay') connection to destination server
|
||||
* In Relay we de-code and re-encode packets
|
||||
*/
|
||||
const relay = new Relay({
|
||||
/* Hostname and port for clients to listen to */
|
||||
hostname: '0.0.0.0',
|
||||
port: 19130,
|
||||
/**
|
||||
* Who does the authentication
|
||||
* If set to `client`, all connecting clients will be sent a message with a link to authenticate
|
||||
* If set to `server`, the server will authenticate and only one client will be able to join
|
||||
* (Default) If set to `none`, no authentication will be done
|
||||
*/
|
||||
auth: 'server',
|
||||
|
||||
/**
|
||||
* Sets if packets will automatically be forwarded. If set to false, you must listen for on('packet')
|
||||
* events and
|
||||
*/
|
||||
auto: true,
|
||||
|
||||
/* Where to send upstream packets to */
|
||||
destination: {
|
||||
hostname: '127.0.0.1',
|
||||
port: 19132,
|
||||
encryption: true
|
||||
}
|
||||
})
|
||||
|
||||
relay.create()
|
||||
}
|
||||
|
||||
createRelay()
|
||||
|
|
@ -90,6 +90,7 @@ class Player extends Connection {
|
|||
// After sending Server to Client Handshake, this handles the client's
|
||||
// Client to Server handshake response. This indicates successful encryption
|
||||
onHandshake() {
|
||||
// this.outLog('Sending login success!', this.status)
|
||||
// https://wiki.vg/Bedrock_Protocol#Play_Status
|
||||
this.write('play_status', { status: 'login_success' })
|
||||
this.status = ClientStatus.Initializing
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ function createEncryptor(client, iv) {
|
|||
|
||||
|
||||
return (blob) => {
|
||||
client.outLog(client.options ? 'C':'S', '🟡 Encrypting', blob)
|
||||
// client.outLog(client.options ? 'C':'S', '🟡 Encrypting', client.sendCounter, blob)
|
||||
// stream.write(blob)
|
||||
process(blob)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue