From b64a22a8fc99d37a8ce27311086d248ac0cf10bf Mon Sep 17 00:00:00 2001 From: extremeheat Date: Thu, 25 Feb 2021 17:47:14 -0500 Subject: [PATCH] protocol updates, movement fixes packet_map is now auto-generated --- data/new/compile.js | 40 ++++-- data/new/packet_map.yml | 6 +- data/new/proto.yml | 86 +++++++++++-- data/new/types.yaml | 139 +++++++++++++++++++++ data/newproto.json | 268 +++++++++++++++++++++++++++++++++++++--- package.json | 1 + src/serverTest.js | 33 +++-- 7 files changed, 531 insertions(+), 42 deletions(-) diff --git a/data/new/compile.js b/data/new/compile.js index 7ffca0e..9ba4669 100644 --- a/data/new/compile.js +++ b/data/new/compile.js @@ -1,17 +1,43 @@ +/** + * This is a utility script that converts the YAML here into ProtoDef schema code and (soon) docs/typescript definitions. + * It also pre-compiles JS code from the schema for easier development. + * + * You can run this with `npm run build` + * + */ const fs = require('fs') const { ProtoDefCompiler } = require('protodef').Compiler -let compile -try { - compile = require('protodef-yaml/compiler').compile -} catch (e) { - require('child_process').execSync('npx protodef-yaml proto.yml protocol.json') -} +function genProtoSchema() { + const { parse, compile } = require('protodef-yaml/compiler') + + // Create the packet_map.yml from proto.yml + const parsed = parse('./proto.yml') + const packets = [] + for (const key in parsed) { + if (key.startsWith('%container')) { + const [, name] = key.split(',') + if (name.startsWith('packet_')) { + const children = parsed[key] + const packetName = name.replace('packet_', '') + const packetID = children['!id'] + packets.push([packetID, packetName, name]) + } + } + } + let l1 = l2 = '' + for (const [id,name,fname] of packets) { + l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n` + l2 += ` if ${name}: ${fname}\n` + } + const t = `!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}` + fs.writeFileSync('./packet_map.yml', t) -if (compile) { compile('./proto.yml', 'protocol.json') } +genProtoSchema() + fs.writeFileSync('../newproto.json', JSON.stringify({ types: require('./protocol.json') }, null, 2)) fs.unlinkSync('./protocol.json') //remove temp file diff --git a/data/new/packet_map.yml b/data/new/packet_map.yml index 6f698c9..ff3e7f0 100644 --- a/data/new/packet_map.yml +++ b/data/new/packet_map.yml @@ -1,4 +1,3 @@ - !import: types.yaml mcpe_packet: name: varint => @@ -64,6 +63,7 @@ mcpe_packet: 0x3e: set_player_game_type 0x3f: player_list 0x40: simple_event + 0x41: event 0x42: spawn_experience_orb 0x43: clientbound_map_item_data 0x44: map_info_request @@ -141,6 +141,7 @@ mcpe_packet: 0x9c: packet_violation_warning 0xa2: item_component 0xa3: filter_text_packet + params: name ? if login: packet_login if play_status: packet_play_status @@ -204,6 +205,7 @@ mcpe_packet: if set_player_game_type: packet_set_player_game_type if player_list: packet_player_list if simple_event: packet_simple_event + if event: packet_event if spawn_experience_orb: packet_spawn_experience_orb if clientbound_map_item_data: packet_clientbound_map_item_data if map_info_request: packet_map_info_request @@ -280,4 +282,4 @@ mcpe_packet: if update_player_game_type: packet_update_player_game_type if packet_violation_warning: packet_packet_violation_warning if item_component: packet_item_component - if filter_text_packet: packet_filter_text_packet \ No newline at end of file + if filter_text_packet: packet_filter_text_packet diff --git a/data/new/proto.yml b/data/new/proto.yml index db42f42..526273f 100644 --- a/data/new/proto.yml +++ b/data/new/proto.yml @@ -453,19 +453,52 @@ packet_move_entity: position: vec3f rotation: Rotation +# MovePlayer is sent by players to send their movement to the server, and by the server to update the +# movement of player entities to other players. packet_move_player: !id: 0x13 !bound: both - runtime_entity_id: varint - x: lf32 - y: lf32 - z: lf32 + # EntityRuntimeID is 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_id: varint + # Position is the position to spawn the player on. If the player is on a distance that the viewer cannot + # see it, the player will still show up if the viewer moves closer. + position: vec3f + # Pitch is the vertical rotation of the player. Facing straight forward yields a pitch of 0. Pitch is + # measured in degrees. pitch: lf32 + # Yaw is the horizontal rotation of the player. Yaw is also measured in degrees yaw: lf32 + # HeadYaw is the same as Yaw, except that it applies specifically to the head of the player. A different + # value for HeadYaw than Yaw means that the player will have its head turned head_yaw: lf32 - mode: u8 + # Mode is the mode of the movement. It specifies the way the player's movement should be shown to other + # players. It is one of the constants below. + mode: u8 => + 0: normal + 1: reset + 2: teleport + 3: rotation + # OnGround specifies if the player is considered on the ground. Note that proxies or hacked clients could + # fake this to always be true, so it should not be taken for granted. on_ground: bool - other_runtime_entity_id: varint + # RiddenEntityRuntimeID is the runtime ID of the entity that the player might currently be riding. If not + # riding, this should be left 0. + ridden_runtime_id: varint + teleport: mode ? + if teleport: + # TeleportCause is written only if Mode is MoveModeTeleport. It specifies the cause of the teleportation, + # which is one of the constants above. + cause: li32 => + 0: unknown + 1: projectile + 2: chorus_fruit + 3: command + 4: behavior + # TeleportSourceEntityType is the entity type that caused the teleportation, for example an ender pearl. + # TODO: is this still a integer and not a string? + source_entity_type: LegacyEntityType + tick: varint64 packet_rider_jump: !id: 0x14 @@ -489,10 +522,20 @@ packet_add_painting: direction: zigzag32 title: string +# TickSync is sent by the client and the server to maintain a synchronized, server-authoritative tick between +# the client and the server. The client sends this packet first, and the server should reply with another one +# of these packets, including the response time. packet_tick_sync: !id: 0x17 !bound: both + # ClientRequestTimestamp is the timestamp on which the client sent this packet to the server. The server + # should fill out that same value when replying. + # The ClientRequestTimestamp is always 0 request_time: li64 + # ServerReceptionTimestamp is the timestamp on which the server received the packet sent by the client. + # When the packet is sent by the client, this value is 0. + # ServerReceptionTimestamp is generally the current tick of the server. It isn't an actual timestamp, as + # the field implies response_time: li64 packet_level_sound_event_old: @@ -1037,6 +1080,33 @@ packet_simple_event: !bound: client event_type: lu16 +# Event is sent by the server to send an event with additional data. It is typically sent to the client for +# telemetry reasons, much like the SimpleEvent packet. +packet_event: + !id: 0x41 + !bound: client + runtime_id: varint64 + event_type: varint => + 0: achievement_awarded + 1: entity_interact + 2: portal_built + 3: portal_used + 4: mob_killed + 5: cauldron_used + 6: player_death + 7: boss_killed + 8: agent_command + 9: agent_created + 10: banner_pattern_removed + 11: commaned_executed + 12: fish_bucketed + 13: mob_born + 14: pet_died + 15: cauldron_block_used + 16: composter_block_used + 17: bell_block_used + use_player_id: u8 + packet_spawn_experience_orb: !id: 0x42 !bound: client @@ -1374,7 +1444,7 @@ packet_update_trade: window_id: u8 # WindowType is an identifier specifying the type of the window opened. In vanilla, it appears this is # always filled out with 15. - window_type: u8 + window_type: WindowType # Size is the amount of trading options that the villager has. size: varint # TradeTier is the tier of the villager that the player is trading with. The tier starts at 0 with a @@ -1412,7 +1482,7 @@ packet_update_equipment: window_id: u8 # WindowType is the type of the window that was opened. Generally, this is the type of a horse inventory, # as the packet is specifically made for that. - window_type: u8 + window_type: WindowType # Size is the size of the horse inventory that should be opened. A bigger size does, in fact, change the # amount of slots displayed. size: u8 diff --git a/data/new/types.yaml b/data/new/types.yaml index 8f6b109..b3418ff 100644 --- a/data/new/types.yaml +++ b/data/new/types.yaml @@ -673,3 +673,142 @@ CommandOrigin: if dev_console or test: player_entity_id: zigzag64 + +WindowType: u8 => + 0: container + 1: workbench + 2: furnace + 3: enchantment + 4: brewing_stand + 5: anvil + 6: dispenser + 7: dropper + 8: hopper + 9: cauldron + 10: minecart_chest + 11: minecart_hopper + 12: horse + 13: beacon + 14: structure_editor + 15: trading + 16: command_block + 17: jukebox + 18: armor + 19: hand + 20: compound_creator + 21: element_constructor + 22: material_reducer + 23: lab_table + 24: loom + 25: lectern + 26: grindstone + 27: blast_furnace + 28: smoker + 29: stonecutter + 30: cartography + 31: hud + 32: jigsaw_editor + 33: smithing_table + +# TODO: remove? +LegacyEntityType: li32 => + 10: chicken + 11: cow + 12: pig + 13: sheep + 14: wolf + 15: villager + 16: mooshroom + 17: squid + 18: rabbit + 19: bat + 20: iron_golem + 21: snow_golem + 22: ocelot + 23: horse + 24: donkey + 25: mule + 26: skeleton_horse + 27: zombie_horse + 28: polar_bear + 29: llama + 30: parrot + 31: dolphin + 32: zombie + 33: creeper + 34: skeleton + 35: spider + 36: zombie_pigman + 37: slime + 38: enderman + 39: silverfish + 40: cave_spider + 41: ghast + 42: magma_cube + 43: blaze + 44: zombie_villager + 45: witch + 46: stray + 47: husk + 48: wither_skeleton + 49: guardian + 50: elder_guardian + 51: npc + 52: wither + 53: ender_dragon + 54: shulker + 55: endermite + 56: agent # LEARN_TO_CODE_MASCOT + 57: vindicator + 58: phantom + 61: armor_stand + 62: tripod_camera + 63: player + 64: item + 65: tnt + 66: falling_block + 67: moving_block + 68: xp_bottle + 69: xp_orb + 70: eye_of_ender_signal + 71: ender_crystal + 72: fireworks_rocket + 73: thrown_trident + 74: turtle + 75: cat + 76: shulker_bullet + 77: fishing_hook + 78: chalkboard + 79: dragon_fireball + 80: arrow + 81: snowball + 82: egg + 83: painting + 84: minecart + 85: fireball + 86: splash_potion + 87: ender_pearl + 88: leash_knot + 89: wither_skull + 90: boat + 91: wither_skull_dangerous + 93: lightning_bolt + 94: small_fireball + 95: area_effect_cloud + 96: hopper_minecart + 97: tnt_minecart + 98: chest_minecart + 100: command_block_minecart + 101: lingering_potion + 102: llama_spit + 103: evocation_fang + 104: evocation_illager + 105: vex + 106: ice_bomb + 107: balloon + 108: pufferfish + 109: salmon + 110: drowned + 111: tropicalfish + 112: cod + 113: panda \ No newline at end of file diff --git a/data/newproto.json b/data/newproto.json index 9779bac..3a31156 100644 --- a/data/newproto.json +++ b/data/newproto.json @@ -2371,6 +2371,156 @@ } ] ], + "WindowType": [ + "mapper", + { + "type": "u8", + "mappings": { + "0": "container", + "1": "workbench", + "2": "furnace", + "3": "enchantment", + "4": "brewing_stand", + "5": "anvil", + "6": "dispenser", + "7": "dropper", + "8": "hopper", + "9": "cauldron", + "10": "minecart_chest", + "11": "minecart_hopper", + "12": "horse", + "13": "beacon", + "14": "structure_editor", + "15": "trading", + "16": "command_block", + "17": "jukebox", + "18": "armor", + "19": "hand", + "20": "compound_creator", + "21": "element_constructor", + "22": "material_reducer", + "23": "lab_table", + "24": "loom", + "25": "lectern", + "26": "grindstone", + "27": "blast_furnace", + "28": "smoker", + "29": "stonecutter", + "30": "cartography", + "31": "hud", + "32": "jigsaw_editor", + "33": "smithing_table" + } + } + ], + "LegacyEntityType": [ + "mapper", + { + "type": "li32", + "mappings": { + "10": "chicken", + "11": "cow", + "12": "pig", + "13": "sheep", + "14": "wolf", + "15": "villager", + "16": "mooshroom", + "17": "squid", + "18": "rabbit", + "19": "bat", + "20": "iron_golem", + "21": "snow_golem", + "22": "ocelot", + "23": "horse", + "24": "donkey", + "25": "mule", + "26": "skeleton_horse", + "27": "zombie_horse", + "28": "polar_bear", + "29": "llama", + "30": "parrot", + "31": "dolphin", + "32": "zombie", + "33": "creeper", + "34": "skeleton", + "35": "spider", + "36": "zombie_pigman", + "37": "slime", + "38": "enderman", + "39": "silverfish", + "40": "cave_spider", + "41": "ghast", + "42": "magma_cube", + "43": "blaze", + "44": "zombie_villager", + "45": "witch", + "46": "stray", + "47": "husk", + "48": "wither_skeleton", + "49": "guardian", + "50": "elder_guardian", + "51": "npc", + "52": "wither", + "53": "ender_dragon", + "54": "shulker", + "55": "endermite", + "56": "agent", + "57": "vindicator", + "58": "phantom", + "61": "armor_stand", + "62": "tripod_camera", + "63": "player", + "64": "item", + "65": "tnt", + "66": "falling_block", + "67": "moving_block", + "68": "xp_bottle", + "69": "xp_orb", + "70": "eye_of_ender_signal", + "71": "ender_crystal", + "72": "fireworks_rocket", + "73": "thrown_trident", + "74": "turtle", + "75": "cat", + "76": "shulker_bullet", + "77": "fishing_hook", + "78": "chalkboard", + "79": "dragon_fireball", + "80": "arrow", + "81": "snowball", + "82": "egg", + "83": "painting", + "84": "minecart", + "85": "fireball", + "86": "splash_potion", + "87": "ender_pearl", + "88": "leash_knot", + "89": "wither_skull", + "90": "boat", + "91": "wither_skull_dangerous", + "93": "lightning_bolt", + "94": "small_fireball", + "95": "area_effect_cloud", + "96": "hopper_minecart", + "97": "tnt_minecart", + "98": "chest_minecart", + "100": "command_block_minecart", + "101": "lingering_potion", + "102": "llama_spit", + "103": "evocation_fang", + "104": "evocation_illager", + "105": "vex", + "106": "ice_bomb", + "107": "balloon", + "108": "pufferfish", + "109": "salmon", + "110": "drowned", + "111": "tropicalfish", + "112": "cod", + "113": "panda" + } + } + ], "mcpe_packet": [ "container", [ @@ -2443,6 +2593,7 @@ "62": "set_player_game_type", "63": "player_list", "64": "simple_event", + "65": "event", "66": "spawn_experience_orb", "67": "clientbound_map_item_data", "68": "map_info_request", @@ -2593,6 +2744,7 @@ "set_player_game_type": "packet_set_player_game_type", "player_list": "packet_player_list", "simple_event": "packet_simple_event", + "event": "packet_event", "spawn_experience_orb": "packet_spawn_experience_orb", "clientbound_map_item_data": "packet_clientbound_map_item_data", "map_info_request": "packet_map_info_request", @@ -3534,20 +3686,12 @@ "container", [ { - "name": "runtime_entity_id", + "name": "runtime_id", "type": "varint" }, { - "name": "x", - "type": "lf32" - }, - { - "name": "y", - "type": "lf32" - }, - { - "name": "z", - "type": "lf32" + "name": "position", + "type": "vec3f" }, { "name": "pitch", @@ -3563,15 +3707,67 @@ }, { "name": "mode", - "type": "u8" + "type": [ + "mapper", + { + "type": "u8", + "mappings": { + "0": "normal", + "1": "reset", + "2": "teleport", + "3": "rotation" + } + } + ] }, { "name": "on_ground", "type": "bool" }, { - "name": "other_runtime_entity_id", + "name": "ridden_runtime_id", "type": "varint" + }, + { + "anon": true, + "type": [ + "switch", + { + "compareTo": "mode", + "fields": { + "teleport": [ + "container", + [ + { + "name": "teleport_cause", + "type": [ + "mapper", + { + "type": "li32", + "mappings": { + "0": "unknown", + "1": "projectile", + "2": "chorus_fruit", + "3": "command", + "4": "behavior" + } + } + ] + }, + { + "name": "source_entity_type", + "type": "LegacyEntityType" + } + ] + ] + }, + "default": "void" + } + ] + }, + { + "name": "tick", + "type": "varint64" } ] ], @@ -4572,6 +4768,48 @@ } ] ], + "packet_event": [ + "container", + [ + { + "name": "runtime_id", + "type": "varint64" + }, + { + "name": "event_type", + "type": [ + "mapper", + { + "type": "varint", + "mappings": { + "0": "achievement_awarded", + "1": "entity_interact", + "2": "portal_built", + "3": "portal_used", + "4": "mob_killed", + "5": "cauldron_used", + "6": "player_death", + "7": "boss_killed", + "8": "agent_command", + "9": "agent_created", + "10": "pattern_removed", + "11": "commaned_executed", + "12": "fish_bucketed", + "13": "mob_born", + "14": "pet_died", + "15": "cauldron_block_used", + "16": "composter_block_used", + "17": "bell_block_used" + } + } + ] + }, + { + "name": "use_player_id", + "type": "u8" + } + ] + ], "packet_spawn_experience_orb": [ "container", [ @@ -5115,7 +5353,7 @@ }, { "name": "window_type", - "type": "u8" + "type": "WindowType" }, { "name": "size", @@ -5160,7 +5398,7 @@ }, { "name": "window_type", - "type": "u8" + "type": "WindowType" }, { "name": "size", diff --git a/package.json b/package.json index 90a49d6..6f9b891 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Parse and serialize Minecraft Bedrock Edition packets", "main": "index.js", "scripts": { + "build": "cd data/new && node compile.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ diff --git a/src/serverTest.js b/src/serverTest.js index 6d01f96..ed6883a 100644 --- a/src/serverTest.js +++ b/src/serverTest.js @@ -54,6 +54,7 @@ server.on('connect', ({ client }) => { client.queue('player_list', require('./packets/player_list.json')) client.queue('start_game', require('./packets/start_game.json')) client.queue('item_component', {"entries":[]}) + client.queue('set_spawn_position', require('./packets/set_spawn_position.json')) client.queue('set_time', { time: 5433771 }) client.queue('set_difficulty', { difficulty: 1 }) client.queue('set_commands_enabled', { enabled: true }) @@ -64,24 +65,28 @@ server.on('connect', ({ client }) => { client.queue('update_attributes', require('./packets/update_attributes.json')) client.queue('creative_content', require('./packets/creative_content.json')) + client.queue('inventory_content', require('./packets/inventory_content.json')) client.queue('player_hotbar', {"selected_slot":3,"window_id":0,"select_slot":true}) client.queue('crafting_data', require('./packets/crafting_data.json')) client.queue('available_commands', require('./packets/available_commands.json')) + client.queue('chunk_radius_update', {"chunk_radius":5}) + + client.queue('set_entity_data', require('./packets/set_entity_data.json')) client.queue('game_rules_changed', require('./packets/game_rules_changed.json')) client.queue('respawn', {"x":646.9405517578125,"y":65.62001037597656,"z":77.86255645751953,"state":0,"runtime_entity_id":0}) - // for (const file of fs.readdirSync('chunks')) { - // const buffer = Buffer.from(fs.readFileSync('./chunks/' + file, 'utf8'), 'hex') - // // console.log('Sending chunk', chunk) - // client.sendBuffer(buffer) - // } - - for (const chunk of chunks) { - client.queue('level_chunk', chunk) + for (const file of fs.readdirSync('chunks')) { + const buffer = Buffer.from(fs.readFileSync('./chunks/' + file, 'utf8'), 'hex') + // console.log('Sending chunk', chunk) + client.sendBuffer(buffer) } + // for (const chunk of chunks) { + // client.queue('level_chunk', chunk) + // } + setInterval(() => { client.write('network_chunk_publisher_update', {"coordinates":{"x":646,"y":130,"z":77},"radius":64}) }, 9500) @@ -90,6 +95,14 @@ server.on('connect', ({ client }) => { setTimeout(() => { client.write('play_status', { status: 'player_spawn' }) }, 8000) + + // Respond to tick synchronization packets + client.on('tick_sync', ({ request_time }) => { + client.queue('tick_sync', { + request_time, + response_time: BigInt(Date.now()) + }) + }) }) }) }) @@ -101,7 +114,7 @@ async function sleep(ms) { } // CHUNKS -const { ChunkColumn, Version } = require('bedrock-provider') +// const { ChunkColumn, Version } = require('bedrock-provider') const mcData = require('minecraft-data')('1.16') var chunks = [] async function buildChunks() { @@ -133,4 +146,4 @@ async function buildChunks() { // console.log('Chunks',chunks) } -buildChunks() \ No newline at end of file +// buildChunks() \ No newline at end of file