Send skin data, protocol updates (#88)
* Add skin data * Serialization updates * Dynamic shield item id * NBT reading/writing on void type uses 0 length, fix some third party servers * Fix proxy empty chunk issue * Fix scoreboards compiler needs ../ * fix indentation * Fix set_score packet * Fix readme title auth doc * Implement new compiler vars
This commit is contained in:
parent
76febb29f1
commit
f0fbf4f859
19 changed files with 5557 additions and 5164 deletions
|
|
@ -45,6 +45,8 @@ const client = bedrock.createClient({
|
|||
port: 19132, // optional, default 19132
|
||||
username: 'Notch', // the username you want to join as, optional if online mode
|
||||
offline: true // optional, default false. if true, do not login with Xbox Live. You will not be asked to sign-in if set to true.
|
||||
// Optional for some servers which verify the title ID:
|
||||
// authTitle: bedrock.title.MinecraftNintendoSwitch
|
||||
})
|
||||
|
||||
client.on('text', (packet) => { // Listen for chat messages and echo them back.
|
||||
|
|
@ -66,8 +68,6 @@ const server = new bedrock.createServer({
|
|||
host: '0.0.0.0', // optional. host to bind as.
|
||||
port: 19132, // optional
|
||||
version: '1.16.220', // optional. The server version, latest if not specified.
|
||||
// Optional for some servers which verify the title ID:
|
||||
// authTitle: bedrock.title.MinecraftNintendoSwitch
|
||||
})
|
||||
|
||||
server.on('connect', client => {
|
||||
|
|
|
|||
Binary file not shown.
4898
data/1.16.201/creativeitems.json
generated
4898
data/1.16.201/creativeitems.json
generated
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
120
data/1.16.201/steve.json
generated
Normal file
120
data/1.16.201/steve.json
generated
Normal file
File diff suppressed because one or more lines are too long
5147
data/1.16.201/steveGeometry.json
generated
Normal file
5147
data/1.16.201/steveGeometry.json
generated
Normal file
File diff suppressed because it is too large
Load diff
BIN
data/1.16.201/steveSkin.bin
Normal file
BIN
data/1.16.201/steveSkin.bin
Normal file
Binary file not shown.
292
data/1.16.220/protocol.json
generated
292
data/1.16.220/protocol.json
generated
|
|
@ -422,7 +422,7 @@
|
|||
{
|
||||
"compareTo": "network_id",
|
||||
"fields": {
|
||||
"355": [
|
||||
"/ShieldItemID": [
|
||||
"encapsulated",
|
||||
{
|
||||
"lengthType": "varint",
|
||||
|
|
@ -502,7 +502,7 @@
|
|||
{
|
||||
"compareTo": "network_id",
|
||||
"fields": {
|
||||
"355": [
|
||||
"/ShieldItemID": [
|
||||
"encapsulated",
|
||||
{
|
||||
"lengthType": "varint",
|
||||
|
|
@ -1933,157 +1933,6 @@
|
|||
}
|
||||
]
|
||||
],
|
||||
"ScoreEntries": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "type",
|
||||
"type": [
|
||||
"mapper",
|
||||
{
|
||||
"type": "u8",
|
||||
"mappings": {
|
||||
"0": "change",
|
||||
"1": "remove"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entries",
|
||||
"type": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"type": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "scoreboard_id",
|
||||
"type": "zigzag64"
|
||||
},
|
||||
{
|
||||
"name": "objective_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "score",
|
||||
"type": "li32"
|
||||
},
|
||||
{
|
||||
"anon": true,
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "type",
|
||||
"fields": {
|
||||
"remove": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "entry_type",
|
||||
"type": [
|
||||
"mapper",
|
||||
{
|
||||
"type": "i8",
|
||||
"mappings": {
|
||||
"1": "player",
|
||||
"2": "entity",
|
||||
"3": "fake_player"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_unique_id",
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "entry_type",
|
||||
"fields": {
|
||||
"player": "zigzag64",
|
||||
"entity": "zigzag64"
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "custom_name",
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "entry_type",
|
||||
"fields": {
|
||||
"fake_player": "string"
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"ScoreboardIdentityEntries": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "type",
|
||||
"type": [
|
||||
"mapper",
|
||||
{
|
||||
"type": "i8",
|
||||
"mappings": {
|
||||
"0": "TYPE_REGISTER_IDENTITY",
|
||||
"1": "TYPE_CLEAR_IDENTITY"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entries",
|
||||
"type": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"type": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "scoreboard_id",
|
||||
"type": "zigzag64"
|
||||
},
|
||||
{
|
||||
"name": "entity_unique_id",
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "type",
|
||||
"fields": {
|
||||
"TYPE_REGISTER_IDENTITY": "zigzag64"
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"Enchant": [
|
||||
"container",
|
||||
[
|
||||
|
|
@ -6982,9 +6831,102 @@
|
|||
"packet_set_score": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "action",
|
||||
"type": [
|
||||
"mapper",
|
||||
{
|
||||
"type": "u8",
|
||||
"mappings": {
|
||||
"0": "change",
|
||||
"1": "remove"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entries",
|
||||
"type": "ScoreEntries"
|
||||
"type": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"type": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "scoreboard_id",
|
||||
"type": "zigzag64"
|
||||
},
|
||||
{
|
||||
"name": "objective_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "score",
|
||||
"type": "li32"
|
||||
},
|
||||
{
|
||||
"anon": true,
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "../action",
|
||||
"fields": {
|
||||
"change": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "entry_type",
|
||||
"type": [
|
||||
"mapper",
|
||||
{
|
||||
"type": "i8",
|
||||
"mappings": {
|
||||
"1": "player",
|
||||
"2": "entity",
|
||||
"3": "fake_player"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_unique_id",
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "entry_type",
|
||||
"fields": {
|
||||
"player": "zigzag64",
|
||||
"entity": "zigzag64"
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "custom_name",
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "entry_type",
|
||||
"fields": {
|
||||
"fake_player": "string"
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
|
|
@ -7146,9 +7088,49 @@
|
|||
"packet_set_scoreboard_identity": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "action",
|
||||
"type": [
|
||||
"mapper",
|
||||
{
|
||||
"type": "i8",
|
||||
"mappings": {
|
||||
"0": "register_identity",
|
||||
"1": "clear_identity"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entries",
|
||||
"type": "ScoreboardIdentityEntries"
|
||||
"type": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"type": [
|
||||
"container",
|
||||
[
|
||||
{
|
||||
"name": "scoreboard_id",
|
||||
"type": "zigzag64"
|
||||
},
|
||||
{
|
||||
"name": "entity_unique_id",
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "../action",
|
||||
"fields": {
|
||||
"register_identity": "zigzag64"
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1854,10 +1854,32 @@ packet_set_display_objective:
|
|||
criteria_name: string
|
||||
sort_order: zigzag32
|
||||
|
||||
# SetScore is sent by the server to send the contents of a scoreboard to the player. It may be used to either
|
||||
# add, remove or edit entries on the scoreboard.
|
||||
packet_set_score:
|
||||
!id: 0x6c
|
||||
!bound: client
|
||||
entries: ScoreEntries
|
||||
# ActionType is the type of the action to execute upon the scoreboard with the entries that the packet
|
||||
# has. If ActionType is ScoreboardActionModify, all entries will be added to the scoreboard if not yet
|
||||
# present, or modified if already present. If set to ScoreboardActionRemove, all scoreboard entries set
|
||||
# will be removed from the scoreboard.
|
||||
action: u8 =>
|
||||
0: change
|
||||
1: remove
|
||||
entries: []varint
|
||||
scoreboard_id: zigzag64
|
||||
objective_name: string
|
||||
score: li32
|
||||
_: ../action ?
|
||||
if change:
|
||||
entry_type: i8 =>
|
||||
1: player
|
||||
2: entity
|
||||
3: fake_player
|
||||
entity_unique_id: entry_type ?
|
||||
if player or entity: zigzag64
|
||||
custom_name: entry_type ?
|
||||
if fake_player: string
|
||||
|
||||
packet_lab_table:
|
||||
!id: 0x6d
|
||||
|
|
@ -1942,10 +1964,27 @@ DeltaMoveFlags: [ "bitflags",
|
|||
}
|
||||
]
|
||||
|
||||
# SetScoreboardIdentity is sent by the server to change the identity type of one of the entries on a
|
||||
# scoreboard. This is used to change, for example, an entry pointing to a player, to a fake player when it
|
||||
# leaves the server, and to change it back to a real player when it joins again.
|
||||
# In non-vanilla situations, the packet is quite useless.
|
||||
packet_set_scoreboard_identity:
|
||||
!id: 0x70
|
||||
!bound: client
|
||||
entries: ScoreboardIdentityEntries
|
||||
# ActionType is the type of the action to execute. The action is either ScoreboardIdentityActionRegister
|
||||
# to associate an identity with the entry, or ScoreboardIdentityActionClear to remove associations with
|
||||
# an entity.
|
||||
action: i8 =>
|
||||
0: register_identity
|
||||
1: clear_identity
|
||||
# Entries is a list of all entries in the packet. Each of these entries points to one of the entries on
|
||||
# a scoreboard. Depending on ActionType, their identity will either be registered or cleared.
|
||||
entries: []varint
|
||||
scoreboard_id: zigzag64
|
||||
entity_unique_id: ../action ?
|
||||
if register_identity: zigzag64
|
||||
default: void
|
||||
|
||||
|
||||
# SetLocalPlayerAsInitialised is sent by the client in response to a PlayStatus packet with the status set
|
||||
# to spawn. The packet marks the moment at which the client is fully initialised and can receive any packet
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ ItemLegacy:
|
|||
metadata: varint
|
||||
block_runtime_id: zigzag32
|
||||
extra: network_id ?
|
||||
if 355: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]'
|
||||
# The Shield Item ID is sent in the StartGame packet. It is usually 355 in vanilla.
|
||||
if /ShieldItemID: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]'
|
||||
default: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithoutBlockingTick" }]'
|
||||
|
||||
# An "ItemStack" here represents an Item instance. You can think about it like a pointer
|
||||
|
|
@ -137,7 +138,9 @@ Item:
|
|||
default: zigzag32
|
||||
block_runtime_id: zigzag32
|
||||
extra: network_id ?
|
||||
if 355: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]'
|
||||
# The Shield Item ID is sent in the StartGame packet. It is usually 355 in vanilla.
|
||||
## Really bad compiler hack to allow us to use a global variable
|
||||
if /ShieldItemID: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]'
|
||||
default: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithoutBlockingTick" }]'
|
||||
|
||||
vec3i:
|
||||
|
|
@ -708,35 +711,6 @@ PlayerRecords:
|
|||
verified: type ?
|
||||
if add: bool[]$records_count
|
||||
|
||||
ScoreEntries:
|
||||
type: u8 =>
|
||||
0: change
|
||||
1: remove
|
||||
entries: []varint
|
||||
scoreboard_id: zigzag64
|
||||
objective_name: string
|
||||
score: li32
|
||||
_: type?
|
||||
if remove:
|
||||
entry_type: i8 =>
|
||||
1: player
|
||||
2: entity
|
||||
3: fake_player
|
||||
entity_unique_id: entry_type?
|
||||
if player or entity: zigzag64
|
||||
custom_name: entry_type?
|
||||
if fake_player: string
|
||||
|
||||
ScoreboardIdentityEntries:
|
||||
type: i8 =>
|
||||
0: TYPE_REGISTER_IDENTITY
|
||||
1: TYPE_CLEAR_IDENTITY
|
||||
entries: []varint
|
||||
scoreboard_id: zigzag64
|
||||
entity_unique_id: type ?
|
||||
if TYPE_REGISTER_IDENTITY: zigzag64
|
||||
default: void
|
||||
|
||||
Enchant:
|
||||
id: u8
|
||||
level: u8
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
"minecraft-folder-path": "^1.1.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"prismarine-nbt": "^1.5.0",
|
||||
"protodef": "extremeheat/node-protodef#patch-1",
|
||||
"protodef": "github:extremeheat/node-protodef#vars",
|
||||
"smart-buffer": "^4.1.0",
|
||||
"uuid-1345": "^1.0.2"
|
||||
},
|
||||
|
|
@ -42,9 +42,9 @@
|
|||
"bedrock-provider": "^1.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"mocha": "^8.3.2",
|
||||
"protodef-yaml": "^1.0.3",
|
||||
"protodef-yaml": "^1.1.0",
|
||||
"standard": "^16.0.3",
|
||||
"leveldb-zlib": "0.0.26",
|
||||
"leveldb-zlib": "^0.0.26",
|
||||
"bedrock-protocol": "file:."
|
||||
},
|
||||
"standard": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const { ClientStatus, Connection } = require('./connection')
|
|||
const { createDeserializer, createSerializer } = require('./transforms/serializer')
|
||||
const { RakClient } = require('./rak')
|
||||
const { serialize } = require('./datatypes/util')
|
||||
const fs = require('fs')
|
||||
const debug = require('debug')('minecraft-protocol')
|
||||
const Options = require('./options')
|
||||
const auth = require('./client/auth')
|
||||
|
|
@ -152,33 +151,16 @@ class Client extends Connection {
|
|||
this.status = ClientStatus.Disconnected
|
||||
}
|
||||
|
||||
tryRencode (name, params, actual) {
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
|
||||
console.assert(packet.equals(actual))
|
||||
if (!packet.equals(actual)) {
|
||||
const ours = packet.toString('hex').match(/.{1,16}/g).join('\n')
|
||||
const theirs = actual.toString('hex').match(/.{1,16}/g).join('\n')
|
||||
|
||||
fs.writeFileSync('ours.txt', ours)
|
||||
fs.writeFileSync('theirs.txt', theirs)
|
||||
fs.writeFileSync('ours.json', serialize(params))
|
||||
fs.writeFileSync('theirs.json', serialize(this.deserializer.parsePacketBuffer(packet).data.params))
|
||||
|
||||
throw new Error(name + ' Packet comparison failed!')
|
||||
}
|
||||
}
|
||||
|
||||
readPacket (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, this.options.loggging ? serialize(pakData.params) : '')
|
||||
this.emit('packet', des)
|
||||
|
||||
if (debugging) {
|
||||
// Packet verifying (decode + re-encode + match test)
|
||||
if (pakData.name) {
|
||||
this.tryRencode(pakData.name, pakData.params, packet)
|
||||
this.deserializer.verify(packet, this.serializer)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +175,12 @@ class Client extends Connection {
|
|||
break
|
||||
case 'start_game':
|
||||
this.startGameData = pakData.params
|
||||
this.startGameData.itemstates.forEach(state => {
|
||||
if (state.name === 'minecraft:shield') {
|
||||
this.serializer.proto.setVariable('ShieldItemID', state.runtime_id)
|
||||
this.deserializer.proto.setVariable('ShieldItemID', state.runtime_id)
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'play_status':
|
||||
if (this.status === ClientStatus.Authenticating) {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,25 @@ class Connection extends EventEmitter {
|
|||
this.encrypt = cipher.createEncryptor(this, iv)
|
||||
}
|
||||
|
||||
updateItemPalette (palette) {
|
||||
// In the future, we can send down the whole item palette if we need
|
||||
// but since it's only one item, we can just make a single variable.
|
||||
let shieldItemID
|
||||
for (const state of palette) {
|
||||
if (state.name === 'minecraft:shield') {
|
||||
shieldItemID = state.runtime_id
|
||||
break
|
||||
}
|
||||
}
|
||||
if (shieldItemID) {
|
||||
this.serializer.proto.setVariable('ShieldItemID', shieldItemID)
|
||||
this.deserializer.proto.setVariable('ShieldItemID', shieldItemID)
|
||||
}
|
||||
}
|
||||
|
||||
write (name, params) {
|
||||
this.outLog('sending', name, params)
|
||||
if (name === 'start_game') this.updateItemPalette(params.itemstates)
|
||||
const batch = new Framer()
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
batch.addEncodedPacket(packet)
|
||||
|
|
@ -55,6 +72,7 @@ class Connection extends EventEmitter {
|
|||
|
||||
queue (name, params) {
|
||||
this.outLog('Q <- ', name, params)
|
||||
if (name === 'start_game') this.updateItemPalette(params.itemstates)
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
if (name === 'level_chunk') {
|
||||
// Skip queue, send ASAP
|
||||
|
|
@ -113,7 +131,11 @@ class Connection extends EventEmitter {
|
|||
|
||||
sendMCPE (buffer, immediate) {
|
||||
if (this.connection.connected === false || this.status === ClientStatus.Disconnected) return
|
||||
this.connection.sendReliable(buffer, immediate)
|
||||
try {
|
||||
this.connection.sendReliable(buffer, immediate)
|
||||
} catch (e) {
|
||||
debug('while sending to', this.connection, e)
|
||||
}
|
||||
}
|
||||
|
||||
// These are callbacks called from encryption.js
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ function connect (client) {
|
|||
response_status: 'completed',
|
||||
resourcepackids: []
|
||||
})
|
||||
client.queue('request_chunk_radius', { chunk_radius: client.renderDistance || 10 })
|
||||
})
|
||||
|
||||
client.queue('client_cache_status', { enabled: false })
|
||||
client.queue('request_chunk_radius', { chunk_radius: client.renderDistance || 1 })
|
||||
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -38,14 +38,18 @@ function sizeOfNbt (value) {
|
|||
// Little Endian
|
||||
|
||||
function readNbtLE (buffer, offset) {
|
||||
return protoLE.read(buffer, offset, 'nbt')
|
||||
const r = protoLE.read(buffer, offset, 'nbt')
|
||||
if (r.value.type === 'end') return { value: r.value, size: 0 }
|
||||
return r
|
||||
}
|
||||
|
||||
function writeNbtLE (value, buffer, offset) {
|
||||
if (value.type === 'end') return offset
|
||||
return protoLE.write(value, buffer, offset, 'nbt')
|
||||
}
|
||||
|
||||
function sizeOfNbtLE (value) {
|
||||
if (value.type === 'end') return 0
|
||||
return protoLE.sizeOf(value, 'nbt')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ const { PUBLIC_KEY } = require('./constants')
|
|||
const algorithm = 'ES384'
|
||||
|
||||
module.exports = (client, server, options) => {
|
||||
const skinGeom = fs.readFileSync(DataProvider(options.protocolVersion).getPath('skin_geom.txt'), 'utf-8')
|
||||
const dp = DataProvider(options.protocolVersion)
|
||||
const skinTex = fs.readFileSync(dp.getPath('steveSkin.bin')).toString('base64')
|
||||
const skinGeom = fs.readFileSync(dp.getPath('steveGeometry.json')).toString('base64')
|
||||
const skinData = JSON.parse(fs.readFileSync(dp.getPath('steve.json'), 'utf-8'))
|
||||
|
||||
client.createClientChain = (mojangKey, offline) => {
|
||||
const privateKey = client.ecdhKeyPair.privateKey
|
||||
|
|
@ -22,12 +25,12 @@ module.exports = (client, server, options) => {
|
|||
certificateAuthority: true,
|
||||
identityPublicKey: client.clientX509
|
||||
}
|
||||
token = JWT.sign(payload, privateKey, { algorithm, notBefore: 0, issuer: 'self', expiresIn: 60 * 60, header: { x5u: client.clientX509 } })
|
||||
token = JWT.sign(payload, privateKey, { algorithm, notBefore: 0, issuer: 'self', expiresIn: 60 * 60, header: { x5u: client.clientX509, typ: undefined } })
|
||||
} else {
|
||||
token = JWT.sign({
|
||||
identityPublicKey: mojangKey || PUBLIC_KEY,
|
||||
certificateAuthority: true
|
||||
}, privateKey, { algorithm, header: { x5u: client.clientX509 } })
|
||||
}, privateKey, { algorithm, header: { x5u: client.clientX509, typ: undefined } })
|
||||
}
|
||||
|
||||
client.clientIdentityChain = token
|
||||
|
|
@ -36,49 +39,38 @@ module.exports = (client, server, options) => {
|
|||
|
||||
client.createClientUserChain = (privateKey) => {
|
||||
let payload = {
|
||||
AnimatedImageData: [],
|
||||
ArmSize: 'wide',
|
||||
CapeData: '',
|
||||
CapeId: '',
|
||||
CapeImageHeight: 0,
|
||||
CapeImageWidth: 0,
|
||||
CapeOnClassicSkin: false,
|
||||
...skinData,
|
||||
|
||||
ClientRandomId: Date.now(),
|
||||
CurrentInputMode: 1,
|
||||
DefaultInputMode: 1,
|
||||
DeviceId: nextUUID(),
|
||||
DeviceModel: '',
|
||||
DeviceModel: 'PrismarineJS',
|
||||
DeviceOS: client.session?.deviceOS || 7,
|
||||
GameVersion: options.version || '1.16.201',
|
||||
GuiScale: -1,
|
||||
LanguageCode: 'en_GB', // TODO locale
|
||||
PersonaPieces: [],
|
||||
PersonaSkin: true,
|
||||
PieceTintColors: [],
|
||||
|
||||
PlatformOfflineId: '',
|
||||
PlatformOnlineId: '', // chat
|
||||
// PlayFabID is the PlayFab ID produced for the skin. PlayFab is the company that hosts the Marketplace,
|
||||
// skins and other related features from the game. This ID is the ID of the skin used to store the skin
|
||||
// inside of PlayFab.
|
||||
PlayFabId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0', // 1.16.210
|
||||
PremiumSkin: false,
|
||||
PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16), // 1.16.210
|
||||
|
||||
SelfSignedId: nextUUID(),
|
||||
ServerAddress: `${options.host}:${options.port}`,
|
||||
SkinAnimationData: '',
|
||||
SkinColor: '#ffffcd96',
|
||||
SkinData: 'AAAAAA==',
|
||||
SkinData: skinTex,
|
||||
SkinGeometryData: skinGeom,
|
||||
SkinId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0',
|
||||
SkinImageHeight: 1,
|
||||
SkinImageWidth: 1,
|
||||
SkinResourcePatch: '',
|
||||
|
||||
ThirdPartyName: client.profile.name,
|
||||
ThirdPartyNameOnly: false,
|
||||
UIProfile: 0
|
||||
}
|
||||
const customPayload = options.skinData || {}
|
||||
payload = { ...payload, ...customPayload }
|
||||
payload.ServerAddress = `${options.host}:${options.port}`
|
||||
|
||||
client.clientUserChain = JWT.sign(payload, privateKey, { algorithm, header: { x5u: client.clientX509 } })
|
||||
client.clientUserChain = JWT.sign(payload, privateKey, { algorithm, header: { x5u: client.clientX509, typ: undefined }, noTimestamp: true /* pocketmine.. */ })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
src/relay.js
49
src/relay.js
|
|
@ -30,6 +30,7 @@ class RelayPlayer extends Player {
|
|||
|
||||
this.outLog = this.downOutLog
|
||||
this.inLog = this.downInLog
|
||||
this.chunkSendCache = []
|
||||
}
|
||||
|
||||
// Called when we get a packet from backend server (Backend -> PROXY -> Client)
|
||||
|
|
@ -45,16 +46,23 @@ class RelayPlayer extends Player {
|
|||
if (name === 'play_status' && params.status === 'login_success') return // We already sent this, this needs to be sent ASAP or client will disconnect
|
||||
|
||||
if (debugging) { // some packet encode/decode testing stuff
|
||||
const rpacket = this.server.serializer.createPacketBuffer({ name, params })
|
||||
if (!rpacket.equals(packet)) {
|
||||
console.warn('New', rpacket.toString('hex'))
|
||||
console.warn('Old', packet.toString('hex'))
|
||||
console.log('Failed to re-encode', name, params)
|
||||
process.exit(1)
|
||||
}
|
||||
this.server.deserializer.verify(des, this.server.serializer)
|
||||
}
|
||||
|
||||
this.emit('clientbound', des.data)
|
||||
|
||||
// If we're sending a chunk, but player isn't yet initialized, wait until it is.
|
||||
// This is wrong and should not be an issue to send chunks before the client
|
||||
// is in the world; need to investigate further, but for now it's fine.
|
||||
if (name === 'level_chunk' && this.status !== 3) {
|
||||
this.chunkSendCache.push([name, params])
|
||||
return
|
||||
} else if (this.status === 3 && this.chunkSendCache.length) {
|
||||
for (const chunk of this.chunkSendCache) {
|
||||
this.queue(...chunk)
|
||||
}
|
||||
this.chunkSendCache = []
|
||||
}
|
||||
this.queue(name, params)
|
||||
}
|
||||
|
||||
|
|
@ -82,33 +90,36 @@ class RelayPlayer extends Player {
|
|||
|
||||
// Called when the server gets a packet from the downstream player (Client -> PROXY -> Backend)
|
||||
readPacket (packet) {
|
||||
if (this.startRelaying) { // The downstream client conn is established & we got a packet to send to upstream server
|
||||
if (!this.upstream) { // Upstream is still connecting/handshaking
|
||||
// The downstream client conn is established & we got a packet to send to upstream server
|
||||
if (this.startRelaying) {
|
||||
// Upstream is still connecting/handshaking
|
||||
if (!this.upstream) {
|
||||
this.downInLog('Got downstream connected packet but upstream is not connected yet, added to q', this.upQ.length)
|
||||
this.upQ.push(packet) // Put into a queue
|
||||
return
|
||||
}
|
||||
this.flushUpQueue() // Send queued packets
|
||||
|
||||
// Send queued packets
|
||||
this.flushUpQueue()
|
||||
this.downInLog('recv', 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
|
||||
const rpacket = this.server.serializer.createPacketBuffer(des.data)
|
||||
if (!rpacket.equals(packet)) {
|
||||
console.warn('New', rpacket.toString('hex'))
|
||||
console.warn('Old', packet.toString('hex'))
|
||||
console.log('Failed to re-encode', des.data)
|
||||
process.exit(1)
|
||||
}
|
||||
this.server.deserializer.verify(des, this.server.serializer)
|
||||
}
|
||||
|
||||
this.emit('serverbound', des.data)
|
||||
|
||||
switch (des.data.name) {
|
||||
case 'client_cache_status':
|
||||
// Force the chunk cache off.
|
||||
this.upstream.queue('client_cache_status', { enabled: false })
|
||||
break
|
||||
case 'set_local_player_as_initialized':
|
||||
this.status = 3
|
||||
break
|
||||
default:
|
||||
// Emit the packet as-is back to the upstream server
|
||||
this.downInLog('Relaying', des.data)
|
||||
|
|
@ -149,6 +160,10 @@ class Relay extends Server {
|
|||
client.outLog = ds.upOutLog
|
||||
client.inLog = ds.upInLog
|
||||
client.once('join', () => { // Intercept once handshaking done
|
||||
// Tell the server to disable chunk cache for this connection as a client.
|
||||
// Wait a bit for the server to ack and process, the continue with proxying
|
||||
// otherwise the player can get stuck in an empty world.
|
||||
client.write('client_cache_status', { enabled: false })
|
||||
ds.upstream = client
|
||||
ds.flushUpQueue()
|
||||
this.conLog('Connected to upstream server')
|
||||
|
|
|
|||
|
|
@ -11,6 +11,18 @@ class Parser extends FullPacketParser {
|
|||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
verify (deserialized, serializer) {
|
||||
const { name, params } = deserialized.data
|
||||
const oldBuffer = deserialized.fullBuffer
|
||||
const newBuffer = serializer.createPacketBuffer({ name, params })
|
||||
if (!newBuffer.equals(oldBuffer)) {
|
||||
console.warn('New', newBuffer.toString('hex'))
|
||||
console.warn('Old', oldBuffer.toString('hex'))
|
||||
console.log('Failed to re-encode', name, params)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compiles the ProtoDef schema at runtime
|
||||
|
|
@ -31,11 +43,8 @@ function getProtocol (version) {
|
|||
compiler.addTypes(require(join(__dirname, '../datatypes/compiler-minecraft')))
|
||||
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
|
||||
|
||||
const compile = (compiler, file) => {
|
||||
global.native = compiler.native // eslint-disable-line
|
||||
const { PartialReadError } = require('protodef/src/utils') // eslint-disable-line
|
||||
return require(file)() // eslint-disable-line
|
||||
}
|
||||
global.PartialReadError = require('protodef/src/utils').PartialReadError
|
||||
const compile = (compiler, file) => require(file)(compiler.native)
|
||||
|
||||
return new CompiledProtodef(
|
||||
compile(compiler.sizeOfCompiler, join(__dirname, `../../data/${version}/size.js`)),
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@ function createProtocol () {
|
|||
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
|
||||
compiler.addTypesToCompile(protocol)
|
||||
|
||||
fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate())
|
||||
fs.writeFileSync('./write.js', 'module.exports = ' + compiler.writeCompiler.generate())
|
||||
fs.writeFileSync('./size.js', 'module.exports = ' + compiler.sizeOfCompiler.generate())
|
||||
fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate().replace('() =>', 'native =>'))
|
||||
fs.writeFileSync('./write.js', 'module.exports = ' + compiler.writeCompiler.generate().replace('() =>', 'native =>'))
|
||||
fs.writeFileSync('./size.js', 'module.exports = ' + compiler.sizeOfCompiler.generate().replace('() =>', 'native =>'))
|
||||
|
||||
const compiledProto = compiler.compileProtoDefSync()
|
||||
return compiledProto
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue