From 1a7205b3d18fc8d6f7b1611d0942a63ff4622eb9 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sun, 7 Feb 2021 19:45:46 -0500 Subject: [PATCH] update protocol, add some doc --- data/new/proto.yml | 240 ++++++++++++++++++++++++++++++++++++++++++-- data/new/types.yaml | 5 + data/newproto.json | 238 +++++++++++++++++++++++++++++++++++++++---- src/server.js | 19 +--- src/serverTest.js | 2 +- 5 files changed, 456 insertions(+), 48 deletions(-) diff --git a/data/new/proto.yml b/data/new/proto.yml index 737c70e..5bf4498 100644 --- a/data/new/proto.yml +++ b/data/new/proto.yml @@ -14,138 +14,356 @@ nbt: native !import: packet_map.yml +!StartDocs: Packets + +# # Login Sequence +# The login process is as follows: +# +# C→S: [Login](#packet_login) +# S→C: [Server To Client Handshake](#packet_server_to_client_handshake) +# C→S: [Client To Server Handshake](#packet_client_to_server_handshake) +# S→C: [Play Status (Login success)](#packet_play_status) +# To spawn, the following packets should be sent, in order, after the ones above: +# +# S→C: [Resource Packs Info](#packet_resource_packs_info) +# C→S: [Resource Pack Client Response](#packet_resource_pack_client_response) +# S→C: [Resource Pack Stack](#packet_resource_pack_stack) +# C→S: [Resource Pack Client Response](#packet_resource_pack_client_response) +# S→C: [Start Game](#packet_start_game) +# S→C: [Creative Content](#packet_creative_content) +# S→C: [Biome Definition List](#packet_biome_definition_list) +# S→C: [Chunks](#packet_level_chunk) +# S→C: [Play Status (Player spawn)](#packet_play_status) +# If there are no resource packs being sent, a Resource Pack Stack can be sent directly +# after Resource Packs Info to avoid the client responses. + + packet_login: !id: 0x01 !bound: client protocol_version: i32 + # The combined size of the `chain` and `client_data` payload_size: varint + # JSON array of JWT data: contains the display name, UUID and XUID + # It should be signed by the Mojang public key chain: LittleString + # Skin related data client_data: LittleString packet_play_status: !id: 0x02 !bound: server - status: i32 + status: i32 => + # Sent after Login has been successfully decoded and the player has logged in + 0: login_success + # Displays "Could not connect: Outdated client!" + 1: failed_client + # Displays "Could not connect: Outdated server!" + 2: failed_spawn + # Sent after world data to spawn the player + 3: player_spawn + # Displays "Unable to connect to world. Your school does not have access to this server." + 4: failed_invalid_tenant + # Displays "The server is not running Minecraft: Education Edition. Failed to connect." + 5: failed_vanilla_edu + # Displays "The server is running an incompatible edition of Minecraft. Failed to connect." + 6: failed_edu_vanilla + # Displays "Wow this server is popular! Check back later to see if space opens up. Server Full" + 7: failed_server_full + packet_server_to_client_handshake: !id: 0x03 !bound: server + # Contains the salt to complete the Diffie-Hellman key exchange token: string + +# Sent by the client in response to a Server To Client Handshake packet +# sent by the server. It is the first encrypted packet in the login handshake +# and serves as a confirmation that encryption is correctly initialized client side. +# It has no fields. packet_client_to_server_handshake: !id: 0x04 !bound: client +# Sent by the server to disconnect a client. packet_disconnect: !id: 0x05 !bound: server + # Specifies if the disconnection screen should be hidden when the client is disconnected, + # meaning it will be sent directly to the main menu. hide_disconnect_reason: bool + # An optional message to show when disconnected. message: string + packet_resource_packs_info: !id: 0x06 !bound: server + # If the resource pack requires the client accept it. must_accept: bool + # If scripting is enabled. has_scripts: bool + # A list of behaviour packs that the client needs to download before joining the server. + # All of these behaviour packs will be applied together. behaviour_packs: BehaviourPackInfos + # A list of resource packs that the client needs to download before joining the server. + # The order of these resource packs is not relevant in this packet. It is however important in the Resource Pack Stack packet. texture_packs: TexturePackInfos packet_resource_pack_stack: !id: 0x07 !bound: server + # If the resource pack must be accepted for the player to join the server. must_accept: bool + # [inline] behavior_packs: ResourcePackIdVersions + # [inline] resource_packs: ResourcePackIdVersions game_version: string - experiments: Experiments - experiments_previously_toggled: bool + experiments: Experiments # ??? such random fields + experiments_previously_used: bool packet_resource_pack_client_response: !id: 0x08 !bound: client - response_status: u8 + response_status: u8 => + 0: none + 1: refused + 2: send_packs + 3: have_all_packs + 4: completed + # All of the pack IDs. resourcepackids: ResourcePackIds +# Sent by the client to the server to send chat messages, and by the server to the client +# to forward or send messages, which may be chat, popups, tips etc. +## https://github.com/pmmp/PocketMine-MP/blob/a43b46a93cb127f037c879b5d8c29cda251dd60c/src/pocketmine/network/mcpe/protocol/TextPacket.php +## https://github.com/Sandertv/gophertunnel/blob/05ac3f843dd60d48b9ca0ab275cda8d9e85d8c43/minecraft/protocol/packet/text.go packet_text: !id: 0x09 !bound: both - type: u8 + type: u8 => + 0: raw + 1: chat + 2: translation + 3: popup + 4: jukebox_popup + 5: tip + 6: system + 7: whisper + 8: announcement + 9: json_whisper + 10: json + # NeedsTranslation specifies if any of the messages need to be translated. It seems that where % is found + # in translatable text types, these are translated regardless of this bool. Translatable text types + # include TextTypeTip, TextTypePopup and TextTypeJukeboxPopup. + needs_translation: bool + _: type? + if chat or whisper or announcement: + sourceName: string + if raw or tip or system or json_whisper or json: + message: string + if translation or popup or jukebox_popup: + message: string + paramaters: string[]varint + # The XUID of the player who sent this message. + xuid: string + # PlatformChatID is an identifier only set for particular platforms when chatting (presumably only for + # Nintendo Switch). It is otherwise an empty string, and is used to decide which players are able to + # chat with each other. + platform_chat_id: string +# For additional information and examples of all the chat types above, see here: https://imgur.com/a/KhcFscg + +# Sent by the server to update the current time client-side. The client actually advances time +# client-side by itself, so this packet does not need to be sent each tick. It is merely a means +# of synchronizing time between server and client. packet_set_time: !id: 0x0a !bound: server + # Time is the current time. The time is not limited to 24000 (time of day), but continues + # progressing after that. time: zigzag32 +# Sent by the server to send information about the world the player will be spawned in. packet_start_game: !id: 0x0b !bound: server + # The unique ID of the player. The unique ID is a value that remains consistent across + # different sessions of the same world, but most unofficial servers simply fill the + # runtime ID of the entity out for this field. entity_id: zigzag64 + # The runtime ID of the player. The runtime ID is unique for each world session, + # and entities are generally identified in packets using this runtime ID. runtime_entity_id: varint player_gamemode: zigzag32 + # The spawn position of the player in the world. In servers this is often the same as the + # world's spawn position found below. spawn: vec3f + # The pitch and yaw of the player rotation: vec3f + # The seed used to generate the world. Unlike in Java edition, the seed is a 32bit Integer here. seed: zigzag32 biome_type: li16 biome_name: string + # Dimension is the ID of the dimension that the player spawns in. It is a value from 0-2, + # with 0 being the overworld, 1 being the nether and 2 being the end. dimension: zigzag32 + # Generator is the generator used for the world. It is a value from 0-4, with 0 being old + # limited worlds, 1 being infinite worlds, 2 being flat worlds, 3 being nether worlds and + # 4 being end worlds. A value of 0 will actually make the client stop rendering chunks you + # send beyond the world limit. generator: zigzag32 + # The game mode that a player gets when it first spawns in the world. It is shown in the + # settings and is used if the Player Gamemode is set to 5. gamemode: zigzag32 + # Difficulty is the difficulty of the world. It is a value from 0-3, with 0 being peaceful, + # 1 being easy, 2 being normal and 3 being hard. difficulty: zigzag32 - x: zigzag32 - y: varint - z: zigzag32 + # The block on which the world spawn of the world. This coordinate has no effect on the place + # that the client spawns, but it does have an effect on the direction that a compass poInts. + spawn_position: BlockCoordinates + # Defines if achievements are disabled in the world. The client crashes if this value is set + # to true while the player's or the world's game mode is creative, and it's recommended to simply + # always set this to false as a server. has_achievements_disabled: bool + # The time at which the day cycle was locked if the day cycle is disabled using the respective + # game rule. The client will maIntain this time as Boolean as the day cycle is disabled. day_cycle_stop_time: zigzag32 + # Some Minecraft: Education Edition field that specifies what 'region' the world was from, + # with 0 being None, 1 being RestOfWorld, and 2 being China. The actual use of this field is unknown. edu_offer: zigzag32 + # Specifies if the world has education edition features enabled, such as the blocks or entities + # specific to education edition. has_edu_features_enabled: bool edu_product_uuid_: string + # The level specifying the Intensity of the rain falling. When set to 0, no rain falls at all. rain_level: lf32 lightning_level: lf32 + # The level specifying the Intensity of the thunder. This may actually be set independently + # from the rain level, meaning dark clouds can be produced without rain. has_confirmed_platform_locked_content: bool + # Specifies if the world is a multi-player game. This should always be set to true for servers. is_multiplayer: bool + # Specifies if LAN broadcast was Intended to be enabled for the world. broadcast_to_lan: bool + # The mode used to broadcast the joined game across XBOX Live. xbox_live_broadcast_mode: varint + # The mode used to broadcast the joined game across the platform. platform_broadcast_mode: varint + # If commands are enabled for the player. It is recommended to always set this to true on the + # server, as setting it to false means the player cannot, under any circumstance, use a command. enable_commands: bool + # Specifies if the texture pack the world might hold is required, meaning the client was + # forced to download it before joining. is_texturepacks_required: bool + # Defines game rules currently active with their respective values. The value of these game + # rules may be either 'bool', 'Int32' or 'Float32'. Some game rules are server side only, + # and don't necessarily need to be sent to the client. gamerules: GameRules experiments: Experiments - experiments_previously_toggled: bool + experiments_previously_used: bool + # Specifies if the world had the bonus map setting enabled when generating it. + # It does not have any effect client-side. bonus_chest: bool + # Specifies if the world has the start with map setting enabled, meaning each + # joining player obtains a map. This should always be set to false, because the + # client obtains a map all on its own accord if this is set to true. map_enabled: bool + # The permission level of the player. It is a value from 0-3, with 0 being visitor, + # 1 being member, 2 being operator and 3 being custom. permission_level: zigzag32 + # The radius around the player in which chunks are ticked. Most servers set this value + # to a fixed number, as it does not necessarily affect anything client-side. server_chunk_tick_range: li32 + # Specifies if the texture pack of the world is locked, meaning it cannot be disabled + # from the world. This is typically set for worlds on the marketplace that have a dedicated + # texture pack. has_locked_behavior_pack: bool + # Specifies if the texture pack of the world is locked, meaning it cannot be disabled from the + # world. This is typically set for worlds on the marketplace that have a dedicated texture pack. has_locked_resource_pack: bool + # Specifies if the world from the server was from a locked world template. + # For servers this should always be set to false. is_from_locked_world_template: bool msa_gamertags_only: bool + # Specifies if the world from the server was from a locked world template. + # For servers this should always be set to false. is_from_world_template: bool + # Specifies if the world was a template that locks all settings that change properties + # above in the settings GUI. It is recommended to set this to true for servers that + # do not allow things such as setting game rules through the GUI. is_world_template_option_locked: bool + # A hack that Mojang put in place to preserve backwards compatibility with old villagers. + # The his never actually read though, so it has no functionality. only_spawn_v1_villagers: bool + # The version of the game from which Vanilla features will be used. + # The exact function of this field isn't clear. game_version: string limited_world_width_: li32 limited_world_length_: li32 is_new_nether_: bool experimental_gameplay_override: bool + # A base64 encoded world ID that is used to identify the world. level_id: string + # The name of the world that the player is joining. Note that this field shows up + # above the player list for the rest of the game session, and cannot be changed. + # Setting the server name to this field is recommended. world_name: string + # A UUID specific to the premium world template that might have been used to + # generate the world. Servers should always fill out an empty String for this. premium_world_template_id: string + # Specifies if the world was a trial world, meaning features are limited and there + # is a time limit on the world. is_trial: bool - movement_type: zigzag32 + # Specifies if the client or server is authoritative over the movement of the player, + # meaning it controls the movement of it. + ## https://github.com/pmmp/PocketMine-MP/blob/a43b46a93cb127f037c879b5d8c29cda251dd60c/src/pocketmine/network/mcpe/protocol/types/PlayerMovementType.php#L26 + movement_authority: zigzag32 => + 0: client + 1: server + # PlayerAuthInputPacket + a bunch of junk that solves a nonexisting problem + 2: server_v2_rewind + # The total time in ticks that has elapsed since the start of the world. current_tick: li64 + # The seed used to seed the random used to produce enchantments in the enchantment table. + # Note that the exact correct random implementation must be used to produce the correct + # results both client- and server-side. enchantment_seed: zigzag32 - block_palette: BlockPalette + + ## This is not sent anymore in protocol versions > 419 (Bedrock Edition v1.16.100) + ## A list of all blocks registered on the server. + ## block_palette: BlockPalette + # A list of all items with their legacy IDs which are available in the game. + # Failing to send any of the items that are in the game will crash mobile clients. itemstates: Itemstates + # A unique ID specifying the multi-player session of the player. + # A random UUID should be filled out for this field. multiplayer_correlation_id: string server_authoritative_inventory: bool packet_add_player: !id: 0x0c !bound: server + # UUID is the UUID of the player. It is the same UUID that the client sent in the + # Login packet at the start of the session. A player with this UUID must exist + # in the player list (built up using the Player List packet) for it to show up in-game. uuid: uuid + # Username is the name of the player. This username is the username that will be + # set as the initial name tag of the player. username: string + # The unique ID of the player. The unique ID is a value that remains consistent + # across different sessions of the same world, but most unoffical servers simply + # fill the runtime ID of the player out for this field. entity_id_self: zigzag64 + # The runtime ID of the player. The runtime ID is unique for each world session, + # and entities are generally identified in packets using this runtime ID. runtime_entity_id: varint + # An identifier only set for particular platforms when chatting (presumably only for + # Nintendo Switch). It is otherwise an empty string, and is used to decide which players + # are able to chat with each other. platform_chat_id: string x: lf32 y: lf32 diff --git a/data/new/types.yaml b/data/new/types.yaml index c84339c..6e32afe 100644 --- a/data/new/types.yaml +++ b/data/new/types.yaml @@ -1,3 +1,5 @@ +!StartDocs: Types + BehaviourPackInfos: []li16 uuid: string version: string @@ -18,8 +20,11 @@ TexturePackInfos: []li16 rtx_enabled: bool ResourcePackIdVersions: []varint + # The ID of the resource pack. uuid: string + # The version of the resource pack. version: string + # The subpack name of the resource pack. name: string ResourcePackIds: string[]varint diff --git a/data/newproto.json b/data/newproto.json index 13ffeaf..5424284 100644 --- a/data/newproto.json +++ b/data/newproto.json @@ -2361,7 +2361,22 @@ [ { "name": "status", - "type": "i32" + "type": [ + "mapper", + { + "type": "i32", + "mappings": { + "0": "login_success", + "1": "failed_client", + "2": "failed_spawn", + "3": "player_spawn", + "4": "failed_invalid_tenant", + "5": "failed_vanilla_edu", + "6": "failed_edu_vanilla", + "7": "failed_server_full" + } + } + ] } ] ], @@ -2436,7 +2451,7 @@ "type": "Experiments" }, { - "name": "experiments_previously_toggled", + "name": "experiments_previously_used", "type": "bool" } ] @@ -2446,7 +2461,19 @@ [ { "name": "response_status", - "type": "u8" + "type": [ + "mapper", + { + "type": "u8", + "mappings": { + "0": "none", + "1": "refused", + "2": "send_packs", + "3": "have_all_packs", + "4": "completed" + } + } + ] }, { "name": "resourcepackids", @@ -2459,7 +2486,178 @@ [ { "name": "type", - "type": "u8" + "type": [ + "mapper", + { + "type": "u8", + "mappings": { + "0": "raw", + "1": "chat", + "2": "translation", + "3": "popup", + "4": "jukebox_popup", + "5": "tip", + "6": "system", + "7": "whisper", + "8": "announcement", + "9": "json_whisper", + "10": "json" + } + } + ] + }, + { + "name": "needs_translation", + "type": "bool" + }, + { + "anon": true, + "type": [ + "switch", + { + "compareTo": "type", + "fields": { + "chat": [ + "container", + [ + { + "name": "sourceName", + "type": "string" + } + ] + ], + "whisper": [ + "container", + [ + { + "name": "sourceName", + "type": "string" + } + ] + ], + "announcement": [ + "container", + [ + { + "name": "sourceName", + "type": "string" + } + ] + ], + "raw": [ + "container", + [ + { + "name": "message", + "type": "string" + } + ] + ], + "tip": [ + "container", + [ + { + "name": "message", + "type": "string" + } + ] + ], + "system": [ + "container", + [ + { + "name": "message", + "type": "string" + } + ] + ], + "json_whisper": [ + "container", + [ + { + "name": "message", + "type": "string" + } + ] + ], + "json": [ + "container", + [ + { + "name": "message", + "type": "string" + } + ] + ], + "translation": [ + "container", + [ + { + "name": "message", + "type": "string" + }, + { + "name": "paramaters", + "type": [ + "array", + { + "countType": "varint", + "type": "string" + } + ] + } + ] + ], + "popup": [ + "container", + [ + { + "name": "message", + "type": "string" + }, + { + "name": "paramaters", + "type": [ + "array", + { + "countType": "varint", + "type": "string" + } + ] + } + ] + ], + "jukebox_popup": [ + "container", + [ + { + "name": "message", + "type": "string" + }, + { + "name": "paramaters", + "type": [ + "array", + { + "countType": "varint", + "type": "string" + } + ] + } + ] + ] + }, + "default": "void" + } + ] + }, + { + "name": "xuid", + "type": "string" + }, + { + "name": "platform_chat_id", + "type": "string" } ] ], @@ -2524,16 +2722,8 @@ "type": "zigzag32" }, { - "name": "x", - "type": "zigzag32" - }, - { - "name": "y", - "type": "varint" - }, - { - "name": "z", - "type": "zigzag32" + "name": "spawn_position", + "type": "BlockCoordinates" }, { "name": "has_achievements_disabled", @@ -2600,7 +2790,7 @@ "type": "Experiments" }, { - "name": "experiments_previously_toggled", + "name": "experiments_previously_used", "type": "bool" }, { @@ -2684,8 +2874,18 @@ "type": "bool" }, { - "name": "movement_type", - "type": "zigzag32" + "name": "movement_authority", + "type": [ + "mapper", + { + "type": "zigzag32", + "mappings": { + "0": "client", + "1": "server", + "2": "server_v2_rewind" + } + } + ] }, { "name": "current_tick", @@ -2695,10 +2895,6 @@ "name": "enchantment_seed", "type": "zigzag32" }, - { - "name": "block_palette", - "type": "BlockPalette" - }, { "name": "itemstates", "type": "Itemstates" diff --git a/src/server.js b/src/server.js index af25535..13fa23b 100644 --- a/src/server.js +++ b/src/server.js @@ -25,17 +25,6 @@ function createDeserializer() { return new Parser(proto, 'mcpe_packet'); } -const PLAY_STATUS = { - 'LoginSuccess': 0, - 'LoginFailedClient': 1, - 'LoginFailedServer': 2, - 'PlayerSpawn': 3, - 'LoginFailedInvalidTenant': 4, - 'LoginFailedVanillaEdu': 5, - 'LoginFailedEduVanilla': 6, - 'LoginFailedServerFull': 7 -} - class Player extends Connection { constructor(server, connection, options) { super() @@ -55,11 +44,11 @@ class Player extends Connection { const clientVer = body.protocol_version if (this.server.options.version) { if (this.server.options.version < clientVer) { - this.sendDisconnectStatus(PLAY_STATUS.LoginFailedClient) + this.sendDisconnectStatus(failed_client) return } } else if (clientVer < MIN_VERSION) { - this.sendDisconnectStatus(PLAY_STATUS.LoginFailedClient) + this.sendDisconnectStatus(failed_client) return } @@ -91,7 +80,7 @@ class Player extends Connection { // Client to Server handshake response. This indicates successful encryption onHandshake() { // https://wiki.vg/Bedrock_Protocol#Play_Status - this.write('play_status', { status: PLAY_STATUS.LoginSuccess }) + this.write('play_status', { status: 'login_success' }) this.emit('join') } @@ -181,4 +170,4 @@ class Server extends EventEmitter { } } -module.exports = { Server, Player, PLAY_STATUS } \ No newline at end of file +module.exports = { Server, Player } \ No newline at end of file diff --git a/src/serverTest.js b/src/serverTest.js index aa356fc..7dccb65 100644 --- a/src/serverTest.js +++ b/src/serverTest.js @@ -33,7 +33,7 @@ server.on('connect', ({ client }) => { 'resource_packs': [], 'game_version': '', 'experiments': [], - 'experiments_previously_toggled': false + 'experiments_previously_used': false }) client.once('resource_pack_client_response', async (packet) => {