* Add relay proxy to tests, docs * Add proxy example, type defs * update docs * proxy: forward login packet, fix offline
155 lines
6.1 KiB
Markdown
155 lines
6.1 KiB
Markdown
CONTRIBUTING.md
|
|
|
|
Contributions are always welcome :)
|
|
|
|
## Updating
|
|
|
|
Good sources for the Minecraft bedrock protocol are [gophertunnel](https://github.com/Sandertv/gophertunnel/tree/master/minecraft/protocol/packet), [ClouburstMC's protocol library](https://github.com/CloudburstMC/Protocol) and [PocketMine](https://github.com/pmmp/PocketMine-MP/tree/stable/src/pocketmine/network/mcpe/protocol).
|
|
|
|
Steps to update:
|
|
* Add the version to src/options.js
|
|
* Open [data/latest/proto.yml](https://github.com/PrismarineJS/bedrock-protocol/tree/new/data/latest) and add, remove or modify the updated packets (see the [Packet serialization](#Packet_serialization) notes at the bottom for info on syntax)
|
|
* Save and make sure to update the !version field at the top of the file
|
|
* Run `npm run build` and `npm test` to test
|
|
|
|
## Code structure
|
|
|
|
The code structure is similar to node-minecraft-protocol. For raknet, raknet-native is used for Raknet communication.
|
|
|
|
## Packet serialization
|
|
|
|
This project uses ProtoDef to serialize and deserialize Minecraft packets. See the documentation [here](https://github.com/ProtoDef-io/node-protodef).
|
|
The ProtoDef schema is JSON can be found [here](https://github.com/PrismarineJS/bedrock-protocol/blob/4169453835790de7eeaa8fb6f5a6b4344f71036b/data/1.16.210/protocol.json) for use in other languages.
|
|
|
|
In bedrock-protocol, JavaScript code is generated from the JSON through the node-protodef compiler.
|
|
|
|
#### YAML syntax
|
|
|
|
For easier maintainability, the JSON is generated from a more human readable YAML format. You can read more [here](https://github.com/extremeheat/protodef-yaml).
|
|
Some documentation is below.
|
|
|
|
Packets should go in proto.yml and extra types should go in types.yml.
|
|
|
|
```yml
|
|
# This defines a new data structure, a ProtoDef container.
|
|
Position:
|
|
# Variable `x` in this struct has a type of `li32`, a little-endian 32-bit integer
|
|
x: li32
|
|
# `z` is a 32-bit LE *unsigned* integer
|
|
z: lu32
|
|
# `b` is a 32-bit LE floating point
|
|
y: lf32
|
|
|
|
# Fields starting with `packet_` are structs representing Minecraft packets
|
|
packet_player_position:
|
|
# Fields starting with ! are ignored by the parser. '!id' is used by the parser when generating the packet map
|
|
!id: 0x29 # This packet is ID #0x29
|
|
!bound: client # `client` or `server` bound, just for documentation purposes. This has no other effect.
|
|
|
|
# Read `on_ground` as a boolean
|
|
on_ground: bool
|
|
# Read `position` as custom data type `Position` defined above.
|
|
position: Position
|
|
|
|
# Reads a 8-bit unsigned integer, then maps it to a string
|
|
movement_reason: u8 =>
|
|
0: player_jump
|
|
1: player_autojump
|
|
2: player_sneak
|
|
3: player_sprint
|
|
4: player_fall
|
|
|
|
# A `_` as a field name declares an anonymous data structure which will be inlined. Adding a '?' at the end will start a `switch` statement
|
|
_: movement_reason ?
|
|
# if the condition matches to the string "player_jump" or "player_autojump", there is a data struct that needs to be read
|
|
if player_jump or player_autojump:
|
|
# read `original_position` as a `Position`
|
|
original_position: Position
|
|
jump_tick: li64
|
|
# if the condition matches "player_fall", read the containing field
|
|
if player_fall:
|
|
original_position: Position
|
|
default: void
|
|
|
|
# Another way to declare a switch, without an anonymous structure. `player_hunger` will be read as a 8-bit int if movement_reason == "player_sprint"
|
|
player_hunger: movement_reason ?
|
|
if player_sprint: u8
|
|
# The default statement as in a switch statement
|
|
default: void
|
|
|
|
# Square brackets notate an array. At the left is the type of the array values, at the right is the type of
|
|
# the length prefix. If no type at the left is specified, the type is defined below.
|
|
|
|
# Reads an array of `Position`, length-prefixed with a ProtoBuf-type unsigned variable length integer (VarInt)
|
|
last_positions: Position[]varint
|
|
|
|
# Reads an array, length-prefixed with a zigzag-encoded signed VarInt
|
|
# The data structure for the array is defined underneath
|
|
keys_down: []zigzag32
|
|
up: bool
|
|
down: bool
|
|
shift: bool
|
|
```
|
|
|
|
The above roughly translates to the following JavaScript code to read a packet:
|
|
```js
|
|
function read_position(stream) {
|
|
const ret = {}
|
|
ret.x = stream.readSignedInt32LE()
|
|
ret.z = stream.readUnsignedInt32LE()
|
|
ret.y = stream.readFloat32LE()
|
|
return ret
|
|
}
|
|
|
|
function read_player_position(stream) {
|
|
const ret = {}
|
|
ret.on_ground = Boolean(stream.readU8())
|
|
ret.position = read_player_position(stream)
|
|
let __movement_reason = stream.readU8()
|
|
let movement_reason = { 0: 'player_jump', 1: 'player_autojump', 2: 'player_sneak', 3: 'player_sprint', 4: 'player_fall' }[__movement_reason]
|
|
switch (movement_reason) {
|
|
case 'player_jump':
|
|
case 'player_autojump':
|
|
ret.original_position = read_player_position(stream)
|
|
ret.jump_tick = stream.readInt64LE(stream)
|
|
break
|
|
case 'player_fall':
|
|
ret.original_position = read_player_position(stream)
|
|
break
|
|
default: break
|
|
}
|
|
ret.player_hunger = undefined
|
|
if (movement_reason == 'player_sprint') ret.player_hunger = stream.readU8()
|
|
ret.last_positions = []
|
|
for (let i = 0; i < stream.readUnsignedVarInt(); i++) {
|
|
ret.last_positions.push(read_player_position(stream))
|
|
}
|
|
ret.keys_down = []
|
|
for (let i = 0; i < stream.readZigZagVarInt(); i++) {
|
|
const ret1 = {}
|
|
ret1.up = Boolean(stream.readU8())
|
|
ret1.down = Boolean(stream.readU8())
|
|
ret1.shift = Boolean(stream.readU8())
|
|
ret.keys_down.push(ret1)
|
|
}
|
|
return ret
|
|
}
|
|
```
|
|
|
|
and the results in the following JSON for the packet:
|
|
```json
|
|
{
|
|
"on_ground": false,
|
|
"position": { "x": 0, "y": 2, "z": 0 },
|
|
"movement_reason": "player_jump",
|
|
"original_position": { "x": 0, "y": 0, "z": 0 },
|
|
"jump_tick": 494894984,
|
|
"last_positions": [{ "x": 0, "y": 1, "z": 0 }],
|
|
"keys_down": []
|
|
}
|
|
```
|
|
|
|
Custom ProtoDef types can be inlined as JSON:
|
|
```yml
|
|
string: ["pstring",{"countType":"varint"}]
|
|
```
|