bedrock-protocol/CONTRIBUTING.md
extremeheat b60fd53ad5
Add relay proxy to tests, docs, fix offline (#71)
* Add relay proxy to tests, docs

* Add proxy example, type defs

* update docs

* proxy: forward login packet, fix offline
2021-04-21 06:22:51 -04:00

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"}]
```