1.16.220 support (#66)

* 1.16.220 initial support

* 1.16.220 fixes, electron gcm

* 1.16.220 item stack fix
This commit is contained in:
extremeheat 2021-04-16 16:40:38 -04:00 committed by GitHub
commit d3723ef42a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 8920 additions and 133 deletions

8315
data/1.16.220/protocol.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,14 @@
# Created from MiNET and gophertunnel docs
# The version below is the latest version this protocol schema was updated for.
# The output protocol.json will be in the folder for the version
!version: 1.16.210
!version: 1.16.220
# Some ProtoDef aliases
string: ["pstring",{"countType":"varint"}]
ByteArray: ["buffer",{"countType":"varint"}]
SignedByteArray: ["buffer",{"countType":"zigzag32"}]
LittleString: ["pstring",{"countType":"li32"}]
ShortArray: ["buffer",{"countType":"li16"}]
varint32: varint
bool: native
zigzag32: native
@ -622,17 +623,45 @@ packet_level_event:
1060: sound_armor_stand_break
1061: sound_armor_stand_hit
1062: sound_armor_stand_fall
1063: sound_armor_stand_place
1063: sound_armor_stand_place
1064: pointed_dripstone_land
1065: dye_used
1066: ink_sack_used
2000: particle_shoot #TODO: check 2000-2017
2001: particle_destroy
2002: particle_splash
2003: particle_eye_despawn
2004: particle_spawn
2006: guardian_curse
2005: particle_crop_growth
2006: particle_guardian_curse
2007: particle_death_smoke
2008: particle_block_force_field
2009: particle_projectile_hit
2009: particle_projectile_hit
2010: particle_dragon_egg_teleport
2011: particle_crop_eaten
2012: particle_critical
2013: particle_enderman_teleport
2014: particle_punch_block
2014: particle_punch_block
2015: particle_bubble
2016: particle_evaporate
2017: particle_destroy_armor_stand
2018: particle_breaking_egg
2019: particle_destroy_egg
2020: particle_evaporate_water
2021: particle_destroy_block_no_sound
2022: particle_knockback_roar
2023: particle_teleport_trail
2024: particle_point_cloud
2025: particle_explosion
2026: particle_block_explosion
2027: particle_vibration_signal
2028: particle_dripstone_drip
2029: particle_fizz_effect
2030: particle_wax_on
2031: particle_wax_off
2032: particle_scrape
2033: particle_electric_spark
3001: start_rain
3002: start_thunder
3003: stop_rain
@ -966,7 +995,7 @@ packet_inventory_slot:
slot: varint
# NewItem is the item to be put in the slot at Slot. It will overwrite any item that may currently
# be present in that slot.
item: ItemStack
item: Item
# ContainerSetData is sent by the server to update specific data of a single container, meaning a block such
# as a furnace or a brewing stand. This data is usually used by the client to display certain features
@ -1385,6 +1414,8 @@ packet_available_commands:
constraints: []varint
constraint: u8 =>
0: cheats_enabled
1: operator_permissions
2: host_permissions
# ParamOptionCollapseEnum specifies if the enum (only if the Type is actually an enum type. If not,
# setting this to true has no effect) should be collapsed. This means that the options of the enum are
@ -1878,10 +1909,14 @@ packet_biome_definition_list:
!bound: client
nbt: nbt
# LevelSoundEvent is sent by the server to make any kind of built-in sound heard to a player. It is sent to,
# for example, play a stepping sound or a shear sound. The packet is also sent by the client, in which case
# it could be forwarded by the server to the other players online. If possible, the packets from the client
# should be ignored however, and the server should play them on its own accord.
packet_level_sound_event:
!id: 0x7b
!bound: both
sound_id: varint
sound_id: SoundType
position: vec3f
block_id: zigzag32
entity_type: string
@ -2210,7 +2245,9 @@ InputFlag: [ "bitflags", {
packet_creative_content:
!id: 0x91
!bound: client
items: ItemStacks
items: []varint
entry_id: varint
item: ItemLegacy
packet_player_enchant_options:
!id: 0x92

View file

@ -75,25 +75,72 @@ Itemstates: []varint
runtime_id: li16
component_based: bool
# Start of item crap ...
ItemExtraDataWithBlockingTick:
has_nbt: lu16 =>
0xffff: 'true'
0x0000: 'false'
nbt: has_nbt ?
if true:
version: u8
nbt: lnbt
default: void
can_place_on: ShortArray[]li32
can_destroy: ShortArray[]li32
blocking_tick: li64
ItemExtraDataWithoutBlockingTick:
has_nbt: lu16 =>
0xffff: 'true'
0x0000: 'false'
nbt: has_nbt ?
if true:
version: u8
nbt: lnbt
default: void
can_place_on: ShortArray[]li32
can_destroy: ShortArray[]li32
# Same as below but without a "networkStackID" boolean ...
ItemLegacy:
network_id: zigzag32
_: network_id?
if 0: void
default:
count: lu16
metadata: varint
block_runtime_id: zigzag32
extra: network_id ?
if 355: '["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
# to an item class. The data for the class gets updated with the data in the `item` field
# As of 1.16.220, now functionally the same as `Item` just without an extra boolean when
# server auth inventories is disabled.
Item:
network_id: zigzag32
_: network_id?
if 0: void
default:
auxiliary_value: zigzag32
has_nbt: lu16 =>
0xffff: 'true'
0x0000: 'false'
nbt: has_nbt?
if true:
version: u8
nbt: nbt
default: void
can_place_on: string[]zigzag32
can_destroy: string[]zigzag32
_: network_id?
if 355:
blocking_tick: zigzag64
count: lu16
metadata: varint
# When server authoritative inventory is enabled, all allocated items have a unique ID used to identify
# a specifc item instance.
has_stack_id: u8
# StackNetworkID is the network ID of the item stack. If the stack is empty, 0 is always written for this
# field. If not, the field should be set to 1 if the server authoritative inventories are disabled in the
# StartGame packet, or to a unique stack ID if it is enabled.
stack_id: has_stack_id ?
if 0: void
default: zigzag32
block_runtime_id: zigzag32
extra: network_id ?
if 355: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]'
default: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithoutBlockingTick" }]'
# end of item crap
vec3i:
x: zigzag32
@ -413,7 +460,7 @@ TransactionUseItem:
2: break_block
# BlockPosition is the position of the block that was interacted with. This is only really a correct
# block position if ActionType is not UseItemActionClickAir.
block_position: BlockCoordinates
block_position: vec3i
# BlockFace is the face of the block that was interacted with. When clicking the block, it is the face
# clicked. When breaking the block, it is the face that was last being hit until the block broke.
face: varint
@ -438,7 +485,6 @@ TransactionUseItem:
# all of these actions results in a balanced inventory transaction. This should be checked to ensure that
# no items are cheated into the inventory.
TransactionActions:
network_ids: bool
actions: []varint
source_type: varint =>
0: container
@ -458,9 +504,6 @@ TransactionActions:
slot: varint
old_item: Item
new_item: Item
new_item_stack_id: ../network_ids?
if true: zigzag32
default: void
# The Minecraft bedrock inventory system was refactored, but not all inventory actions use the new packet.
# This data structure holds actions that have not been updated to the new system.
@ -542,17 +585,7 @@ Transaction:
# mainly for purposes such as spawning eating particles at that position.
head_pos: vec3f
# An "ItemStack" here represents an Item instance. You can think about it like a pointer
# to an item class. The data for the class gets updated with the data in the `item` field
ItemStack:
# StackNetworkID is the network ID of the item stack. If the stack is empty, 0 is always written for this
# field. If not, the field should be set to 1 if the server authoritative inventories are disabled in the
# StartGame packet, or to a unique stack ID if it is enabled.
stack_id: varint
# Stack is the actual item stack of the item instance.
item: Item
ItemStacks: ItemStack[]varint
ItemStacks: Item[]varint
RecipeIngredient:
network_id: zigzag32
@ -591,7 +624,7 @@ Recipes: []varint
if shapeless or shulker_box or shapeless_chemistry:
recipe_id: string
input: RecipeIngredient[]varint
output: Item[]varint
output: ItemLegacy[]varint
uuid: uuid
block: string
priority: zigzag32
@ -604,19 +637,19 @@ Recipes: []varint
# RecipeIngredient[$height][$width] or RecipeIngredient[]$height[]$width ?
input: []$width
_: RecipeIngredient[]$height
output: Item[]varint
output: ItemLegacy[]varint
uuid: uuid
block: string
priority: zigzag32
network_id: varint
if furnace:
input_id: zigzag32
output: Item
output: ItemLegacy
block: string
if furnace_with_metadata:
input_id: zigzag32
input_meta: zigzag32
output: Item
output: ItemLegacy
block: string
if multi:
uuid: uuid
@ -889,7 +922,7 @@ ItemStackRequest:
filtered_string_index: li32
if non_implemented: void
if results_deprecated:
result_items: Item[]varint
result_items: ItemLegacy[]varint
times_crafted: u8
# CustomNames is a list of custom names involved in the request. This is typically filled with one string
# when an anvil is used.
@ -959,6 +992,10 @@ CommandOrigin:
9: virtual
10: game_argument
11: entity_server
12: precompiled
13: game_director_entity_server # ?
14: script
# UUID is the UUID of the command called. This UUID is a bit odd as it is not specified by the server. It
# is not clear what exactly this UUID is meant to identify, but it is unique for each command called.
uuid: uuid
@ -1135,6 +1172,344 @@ ContainerSlotType: u8 =>
- cursor
- creative_output
SoundType: varint =>
- ItemUseOn
- Hit
- Step
- Fly
- Jump
- Break
- Place
- HeavyStep
- Gallop
- Fall
- Ambient
- AmbientBaby
- AmbientInWater
- Breathe
- Death
- DeathInWater
- DeathToZombie
- Hurt
- HurtInWater
- Mad
- Boost
- Bow
- SquishBig
- SquishSmall
- FallBig
- FallSmall
- Splash
- Fizz
- Flap
- Swim
- Drink
- Eat
- Takeoff
- Shake
- Plop
- Land
- Saddle
- Armor
- MobArmorStandPlace
- AddChest
- Throw
- Attack
- AttackNoDamage
- AttackStrong
- Warn
- Shear
- Milk
- Thunder
- Explode
- Fire
- Ignite
- Fuse
- Stare
- Spawn
- Shoot
- BreakBlock
- Launch
- Blast
- LargeBlast
- Twinkle
- Remedy
- Infect
- LevelUp
- BowHit
- BulletHit
- ExtinguishFire
- ItemFizz
- ChestOpen
- ChestClosed
- ShulkerBoxOpen
- ShulkerBoxClosed
- EnderChestOpen
- EnderChestClosed
- PowerOn
- PowerOff
- Attach
- Detach
- Deny
- Tripod
- Pop
- DropSlot
- Note
- Thorns
- PistonIn
- PistonOut
- Portal
- Water
- LavaPop
- Lava
- Burp
- BucketFillWater
- BucketFillLava
- BucketEmptyWater
- BucketEmptyLava
- ArmorEquipChain
- ArmorEquipDiamond
- ArmorEquipGeneric
- ArmorEquipGold
- ArmorEquipIron
- ArmorEquipLeather
- ArmorEquipElytra
- Record13
- RecordCat
- RecordBlocks
- RecordChirp
- RecordFar
- RecordMall
- RecordMellohi
- RecordStal
- RecordStrad
- RecordWard
- Record11
- RecordWait
- unknown1
- Flop
- ElderGuardianCurse
- MobWarning
- MobWarningBaby
- Teleport
- ShulkerOpen
- ShulkerClose
- Haggle
- HaggleYes
- HaggleNo
- HaggleIdle
- ChorusGrow
- ChorusDeath
- Glass
- PotionBrewed
- CastSpell
- PrepareAttack
- PrepareSummon
- PrepareWololo
- Fang
- Charge
- CameraTakePicture
- LeashKnotPlace
- LeashKnotBreak
- Growl
- Whine
- Pant
- Purr
- Purreow
- DeathMinVolume
- DeathMidVolume
- unknown2
- ImitateCaveSpider
- ImitateCreeper
- ImitateElderGuardian
- ImitateEnderDragon
- ImitateEnderman
- unknown3
- ImitateEvocationIllager
- ImitateGhast
- ImitateHusk
- ImitateIllusionIllager
- ImitateMagmaCube
- ImitatePolarBear
- ImitateShulker
- ImitateSilverfish
- ImitateSkeleton
- ImitateSlime
- ImitateSpider
- ImitateStray
- ImitateVex
- ImitateVindicationIllager
- ImitateWitch
- ImitateWither
- ImitateWitherSkeleton
- ImitateWolf
- ImitateZombie
- ImitateZombiePigman
- ImitateZombieVillager
- BlockEndPortalFrameFill
- BlockEndPortalSpawn
- RandomAnvilUse
- BottleDragonBreath
- PortalTravel
- ItemTridentHit
- ItemTridentReturn
- ItemTridentRiptide1
- ItemTridentRiptide2
- ItemTridentRiptide3
- ItemTridentThrow
- ItemTridentThunder
- ItemTridentHitGround
- Default
- BlockFletchingTableUse
- ElemConstructOpen
- IceBombHit
- BalloonPop
- LtReactionIceBomb
- LtReactionBleach
- LtReactionEPaste
- LtReactionEPaste2
- LtReactionFertilizer
- LtReactionFireball
- LtReactionMgsalt
- LtReactionMiscfire
- LtReactionFire
- LtReactionMiscexplosion
- LtReactionMiscmystical
- LtReactionMiscmystical2
- LtReactionProduct
- SparklerUse
- GlowstickUse
- SparklerActive
- ConvertToDrowned
- BucketFillFish
- BucketEmptyFish
- BubbleUp
- BubbleDown
- BubblePop
- BubbleUpInside
- BubbleDownInside
- HurtBaby
- DeathBaby
- StepBaby
- BabySpawn
- Born
- BlockTurtleEggBreak
- BlockTurtleEggCrack
- BlockTurtleEggHatch
- TurtleLayEgg
- BlockTurtleEggAttack
- BeaconActivate
- BeaconAmbient
- BeaconDeactivate
- BeaconPower
- ConduitActivate
- ConduitAmbient
- ConduitAttack
- ConduitDeactivate
- ConduitShort
- Swoop
- BlockBambooSaplingPlace
- PreSneeze
- Sneeze
- AmbientTame
- Scared
- BlockScaffoldingClimb
- CrossbowLoadingStart
- CrossbowLoadingMiddle
- CrossbowLoadingEnd
- CrossbowShoot
- CrossbowQuickChargeStart
- CrossbowQuickChargeMiddle
- CrossbowQuickChargeEnd
- AmbientAggressive
- AmbientWorried
- CantBreed
- ItemShieldBlock
- ItemBookPut
- BlockGrindstoneUse
- BlockBellHit
- BlockCampfireCrackle
- Roar
- Stun
- BlockSweetBerryBushHurt
- BlockSweetBerryBushPick
- UICartographyTableTakeResult
- UIStoneCutterTakeResult
- BlockComposterEmpty
- BlockComposterFill
- BlockComposterFillSuccess
- BlockComposterReady
- BlockBarrelOpen
- BlockBarrelClose
- RaidHorn
- BlockLoomUse
- AmbientRaid
- UICartographyTableUse
- UIStoneCutterUse
- UILoomUse
- SmokerUse
- BlastFurnaceUse
- SmithingTableUse
- Screech
- Sleep
- FurnaceUse
- MooshroomConvert
- MilkSuspiciously
- Celebrate
- JumpPrevent
- AmbientPollinate
- BeeHiveDrip
- BeeHiveEnter
- BeeHiveExit
- BeeHiveWork
- BeeHiveShear
- HoneyBottleDrink
- AmbientCave
- Retreat
- ConvertToZombified
- Admire
- StepLava
- Tempt
- Panic
- Angry
- AmbientWarpedForest
- AmbientSoulsandValley
- AmbientNetherWastes
- AmbientBasaltDeltas
- AmbientCrimsonForest
- RespawnAnchorCharge
- RespawnAnchorDeplete
- RespawnAnchorSetSpawn
- RespawnAnchorAmbient
- SoulEscapeQuiet
- SoulEscapeLoud
- RecordPigstep
- LinkCompassToLodestone
- BlockSmithingTableUse
- EquipNetherite
- AmbientLoopWarpedForest
- AmbientLoopSoulsandValley
- AmbientLoopNetherWastes
- AmbientLoopBasaltDeltas
- AmbientLoopCrimsonForest
- AmbientAdditionWarpedForest
- AmbientAdditionSoulsandValley
- AmbientAdditionNetherWastes
- AmbientAdditionBasaltDeltas
- AmbientAdditionCrimsonForest
- SculkSensorPowerOn
- SculkSensorPowerOff
- BucketFillPowderSnow
- BucketEmptyPowderSnow
- PointedDripstoneCauldronDripWater
- PointedDripstoneCauldronDripLava
- PointedDripstoneDripWater
- PointedDripstoneDripLava
- CaveVinesPickBerries
- BigDripleafTiltDown
- BigDripleafTiltUp
- Undefined
# TODO: remove?
LegacyEntityType: li32 =>
10: chicken

View file

@ -1,7 +1,7 @@
{
"name": "bedrock-protocol",
"version": "3.0.0",
"description": "Parse and serialize Minecraft Bedrock Edition packets",
"description": "Minecraft Bedrock Edition protocol library",
"main": "index.js",
"scripts": {
"build": "cd tools && node compileProtocol.js",
@ -15,6 +15,7 @@
},
"keywords": [
"minecraft",
"bedrock",
"pocket-edition",
"protocol"
],
@ -24,27 +25,25 @@
"@jsprismarine/jsbinaryutils": "^2.1.8",
"@xboxreplay/xboxlive-auth": "^3.3.3",
"asn1": "^0.2.4",
"browserify-cipher": "^1.0.1",
"bedrock-provider": "^1.0.0",
"debug": "^4.3.1",
"ec-pem": "^0.18.0",
"jsonwebtoken": "^8.5.1",
"jsp-raknet": "github:extremeheat/raknet#client",
"leveldb-zlib": "0.0.26",
"minecraft-folder-path": "^1.1.0",
"node-fetch": "^2.6.1",
"prismarine-nbt": "^1.5.0",
"protodef": "^1.11.0",
"raknet-native": "^1.0.0",
"raknet-native": "^1.0.1",
"uuid-1345": "^1.0.2"
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.10",
"bedrock-provider": "^1.0.0",
"babel-eslint": "^10.1.0",
"buffer-equal": "^1.0.0",
"mocha": "^8.3.2",
"protodef-yaml": "^1.0.2",
"protodef-yaml": "^1.0.3",
"standard": "^16.0.3",
"leveldb-zlib": "0.0.26",
"bedrock-protocol": "file:."
},
"standard": {

View file

@ -2,7 +2,7 @@ const BinaryStream = require('@jsprismarine/jsbinaryutils').default
const BatchPacket = require('./datatypes/BatchPacket')
const cipher = require('./transforms/encryption')
const { EventEmitter } = require('events')
const Versions = require('./options')
const { Versions } = require('./options')
const debug = require('debug')('minecraft-protocol')
const SKIP_BATCH = ['level_chunk', 'client_cache_blob_status', 'client_cache_miss_response']
@ -29,19 +29,11 @@ class Connection extends EventEmitter {
}
versionLessThan (version) {
if (typeof version === 'string') {
return Versions[version] < this.options.protocolVersion
} else {
return version < this.options.protocolVersion
}
return this.options.protocolVersion < (typeof version === 'string' ? Versions[version] : version)
}
versionGreaterThan (version) {
if (typeof version === 'string') {
return Versions[version] > this.options.protocolVersion
} else {
return version > this.options.protocolVersion
}
return this.options.protocolVersion > (typeof version === 'string' ? Versions[version] : version)
}
startEncryption (iv) {

View file

@ -36,28 +36,48 @@ SizeOf.restBuffer = ['native', (value) => {
return value.length
}]
/**
* Encapsulated data with length prefix
*/
Read.encapsulated = ['parametrizable', (compiler, { lengthType, type }) => {
return compiler.wrapCode(`
const payloadSize = ${compiler.callType(lengthType, 'offset')}
const { value, size } = ctx.${type}(buffer, offset + payloadSize.size)
return { value, size: size + payloadSize.size }
`.trim())
}]
Write.encapsulated = ['parametrizable', (compiler, { lengthType, type }) => {
return compiler.wrapCode(`
const buf = Buffer.allocUnsafe(buffer.length - offset)
const payloadSize = (ctx.${type})(value, buf, 0)
let size = (ctx.${lengthType})(payloadSize, buffer, offset)
size += buf.copy(buffer, size, 0, payloadSize)
return size
`.trim())
}]
SizeOf.encapsulated = ['parametrizable', (compiler, { lengthType, type }) => {
return compiler.wrapCode(`
const payloadSize = (ctx.${type})(value)
return (ctx.${lengthType})(payloadSize) + payloadSize
`.trim())
}]
/**
* Read NBT until end of buffer or \0
*/
Read.nbtLoop = ['context', (buffer, offset) => {
const values = []
while (buffer[offset] != 0) {
// console.log('offs',offset, buffer.length,buffer.slice(offset))
const n = ctx.nbt(buffer, offset)
// console.log('read',n)
values.push(n.value)
offset += n.size
}
// console.log('Ext',offset, buffer.length,buffer.slice(offset))
return { value: values, size: buffer.length - offset }
}]
Write.nbtLoop = ['context', (value, buffer, offset) => {
for (const val of value) {
// console.log('val',val,offset)
offset = ctx.nbt(val, buffer, offset)
}
// offset += 1
// console.log('writing 0', offset)
buffer.writeUint8(0, offset)
return offset + 1
}]
@ -76,6 +96,10 @@ Read.nbt = ['native', minecraft.nbt[0]]
Write.nbt = ['native', minecraft.nbt[1]]
SizeOf.nbt = ['native', minecraft.nbt[2]]
Read.lnbt = ['native', minecraft.lnbt[0]]
Write.lnbt = ['native', minecraft.lnbt[1]]
SizeOf.lnbt = ['native', minecraft.lnbt[2]]
/**
* Bits
*/
@ -84,7 +108,7 @@ Read.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
let fstr = JSON.stringify(flags)
if (Array.isArray(flags)) {
fstr = '{'
flags.map((v,k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
fstr += '}'
} else if (shift) {
fstr = '{'
@ -106,7 +130,7 @@ Write.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
let fstr = JSON.stringify(flags)
if (Array.isArray(flags)) {
fstr = '{'
flags.map((v,k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
fstr += '}'
} else if (shift) {
fstr = '{'
@ -127,7 +151,7 @@ SizeOf.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) =>
let fstr = JSON.stringify(flags)
if (Array.isArray(flags)) {
fstr = '{'
flags.map((v,k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
fstr += '}'
} else if (shift) {
fstr = '{'

View file

@ -2,7 +2,8 @@
const nbt = require('prismarine-nbt')
const UUID = require('uuid-1345')
const proto = nbt.protos.littleVarint
const protoLE = nbt.protos.little
const protoLEV = nbt.protos.littleVarint
// TODO: deal with this:
const zigzag = require('prismarine-nbt/compiler-zigzag')
@ -20,16 +21,32 @@ function writeUUID (value, buffer, offset) {
return offset + 16
}
// Little Endian + varints
function readNbt (buffer, offset) {
return proto.read(buffer, offset, 'nbt')
return protoLEV.read(buffer, offset, 'nbt')
}
function writeNbt (value, buffer, offset) {
return proto.write(value, buffer, offset, 'nbt')
return protoLEV.write(value, buffer, offset, 'nbt')
}
function sizeOfNbt (value) {
return proto.sizeOf(value, 'nbt')
return protoLEV.sizeOf(value, 'nbt')
}
// Little Endian
function readNbtLE (buffer, offset) {
return protoLE.read(buffer, offset, 'nbt')
}
function writeNbtLE (value, buffer, offset) {
return protoLE.write(value, buffer, offset, 'nbt')
}
function sizeOfNbtLE (value) {
return protoLE.sizeOf(value, 'nbt')
}
function readEntityMetadata (buffer, offset, _ref) {
@ -131,6 +148,7 @@ function sizeOfEndOfArray (value, typeArgs) {
module.exports = {
uuid: [readUUID, writeUUID, 16],
nbt: [readNbt, writeNbt, sizeOfNbt],
lnbt: [readNbtLE, writeNbtLE, sizeOfNbtLE],
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
ipAddress: [readIpAddress, writeIpAddress, 4],
endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray],

View file

@ -1,9 +1,10 @@
// Minimum supported version (< will be kicked)
const MIN_VERSION = '1.16.201'
// Currently supported verson
const CURRENT_VERSION = '1.16.210'
const CURRENT_VERSION = '1.16.220'
const Versions = {
'1.16.220': 431,
'1.16.210': 428,
'1.16.201': 422
}

View file

@ -1,28 +1,26 @@
const { Transform } = require('readable-stream')
const crypto = require('crypto')
const Zlib = require('zlib')
if (globalThis.isElectron) var { CipherCFB8 } = require('raknet-native') // eslint-disable-line
if (globalThis.isElectron) var { CipherGCM, CipherCFB8 } = require('raknet-native') // eslint-disable-line
const CIPHER_ALG = 'aes-256-cfb8'
function createCipher (secret, initialValue) {
if (crypto.getCiphers().includes(CIPHER_ALG)) {
return crypto.createCipheriv(CIPHER_ALG, secret, initialValue)
function createCipher (secret, initialValue, cipherAlgorithm) {
if (crypto.getCiphers().includes(cipherAlgorithm)) {
return crypto.createCipheriv(cipherAlgorithm, secret, initialValue)
}
return new Cipher(secret, initialValue)
}
function createDecipher (secret, initialValue) {
if (crypto.getCiphers().includes(CIPHER_ALG)) {
return crypto.createDecipheriv(CIPHER_ALG, secret, initialValue)
function createDecipher (secret, initialValue, cipherAlgorithm) {
if (crypto.getCiphers().includes(cipherAlgorithm)) {
return crypto.createDecipheriv(cipherAlgorithm, secret, initialValue)
}
return new Decipher(secret, initialValue)
}
class Cipher extends Transform {
constructor (secret, iv) {
constructor (gcm, secret, iv) {
super()
this.aes = new CipherCFB8(secret, iv)
this.aes = gcm ? new CipherGCM(secret, iv) : new CipherCFB8(secret, iv)
}
_transform (chunk, enc, cb) {
@ -32,9 +30,9 @@ class Cipher extends Transform {
}
class Decipher extends Transform {
constructor (secret, iv) {
constructor (gcm, secret, iv) {
super()
this.aes = new CipherCFB8(secret, iv)
this.aes = gcm ? new CipherGCM(secret, iv) : new CipherCFB8(secret, iv)
}
_transform (chunk, enc, cb) {
@ -54,7 +52,11 @@ function computeCheckSum (packetPlaintext, sendCounter, secretKeyBytes) {
}
function createEncryptor (client, iv) {
client.cipher = createCipher(client.secretKeyBytes, iv)
if (client.versionLessThan('1.16.220')) {
client.cipher = createCipher(client.secretKeyBytes, iv, 'aes-256-cfb8')
} else {
client.cipher = createCipher(client.secretKeyBytes, iv.slice(0, 12), 'aes-256-gcm')
}
client.sendCounter = client.sendCounter || 0n
// A packet is encrypted via AES256(plaintext + SHA256(send_counter + plaintext + secret_key)[0:8]).
@ -77,18 +79,21 @@ function createEncryptor (client, iv) {
}
function createDecryptor (client, iv) {
client.decipher = createDecipher(client.secretKeyBytes, iv)
if (client.versionLessThan('1.16.220')) {
client.decipher = createDecipher(client.secretKeyBytes, iv, 'aes-256-cfb8')
} else {
client.decipher = createDecipher(client.secretKeyBytes, iv.slice(0, 12), 'aes-256-gcm')
}
client.receiveCounter = client.receiveCounter || 0n
function verify (chunk) {
// console.log('Decryptor: checking checksum', client.receiveCounter, chunk)
const packet = chunk.slice(0, chunk.length - 8)
const checksum = chunk.slice(chunk.length - 8, chunk.length)
const computedCheckSum = computeCheckSum(packet, client.receiveCounter, client.secretKeyBytes)
client.receiveCounter++
if (Buffer.compare(checksum, computedCheckSum) !== 0) {
// console.log('Inflated', inflatedLen, chunk.length, extraneousLen, chunk.toString('hex'))
throw Error(`Checksum mismatch ${checksum.toString('hex')} != ${computedCheckSum.toString('hex')}`)
}
@ -108,17 +113,3 @@ function createDecryptor (client, iv) {
module.exports = {
createCipher, createDecipher, createEncryptor, createDecryptor
}
// function testDecrypt () {
// const client = {
// secretKeyBytes: Buffer.from('ZOBpyzki/M8UZv5tiBih048eYOBVPkQE3r5Fl0gmUP4=', 'base64'),
// onDecryptedPacket: (...data) => console.log('Decrypted', data)
// }
// const iv = Buffer.from('ZOBpyzki/M8UZv5tiBih0w==', 'base64')
// const decrypt = createDecryptor(client, iv)
// console.log('Dec', decrypt(Buffer.from('4B4FCA0C2A4114155D67F8092154AAA5EF', 'hex')))
// console.log('Dec 2', decrypt(Buffer.from('DF53B9764DB48252FA1AE3AEE4', 'hex')))
// }
// testDecrypt()

View file

@ -1,17 +1,15 @@
// process.env.DEBUG = 'minecraft-protocol raknet'
const { Server, Client } = require('../')
const { dumpPackets, hasDumps } = require('../tools/genPacketDumps')
const { dumpPackets } = require('../tools/genPacketDumps')
const DataProvider = require('../data/provider')
// First we need to dump some packets that a vanilla server would send a vanilla
// client. Then we can replay those back in our custom server.
function prepare (version) {
if (!hasDumps(version)) {
return dumpPackets(version)
}
return dumpPackets(version)
}
async function startTest (version = '1.16.210', ok) {
async function startTest (version = '1.16.201', ok) {
await prepare(version)
const Item = require('../types/Item')(version)
const port = 19130
@ -36,7 +34,7 @@ async function startTest (version = '1.16.210', ok) {
// server logic
server.on('connect', client => {
client.on('join', () => {
console.log('Client joined', client.getData())
console.log('Client joined server', client.getData())
client.write('resource_packs_info', {
must_accept: false,

View file

@ -1,11 +1,15 @@
/* eslint-env jest */
const { timedTest } = require('./internal')
const { Versions } = require('../src/options')
describe('internal client/server test', function () {
this.timeout(120 * 1000)
it('connects', async () => {
await timedTest()
for (const version in Versions) {
console.debug(version)
await timedTest(version)
}
})
})

View file

@ -3,10 +3,11 @@ const vanillaServer = require('../tools/startVanillaServer')
const { Client } = require('../src/client')
const { waitFor } = require('../src/datatypes/util')
const { ChunkColumn, Version } = require('bedrock-provider')
const { CURRENT_VERSION } = require('../src/options')
async function test (version) {
// Start the server, wait for it to accept clients, throws on timeout
const handle = await vanillaServer.startServerAndWait(version, 1000 * 120)
const handle = await vanillaServer.startServerAndWait(version, 1000 * 220)
console.log('Started server')
const client = new Client({
@ -65,5 +66,5 @@ async function test (version) {
clearInterval(loop)
}
if (!module.parent) test()
if (!module.parent) test(CURRENT_VERSION)
module.exports = { clientTest: test }

View file

@ -66,6 +66,7 @@ function createProtocol () {
function copyLatest () {
process.chdir(join(__dirname, '/../data/latest'))
const version = genProtoSchema()
try { fs.mkdirSync(`../${version}`) } catch {}
fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: getJSON('./proto.json') }, null, 2))
fs.unlinkSync('./proto.json') // remove temp file
fs.unlinkSync('./packet_map.yml') // remove temp file

View file

@ -27,6 +27,7 @@ async function dump (version, force) {
const client = new Client({
hostname: '127.0.0.1',
port,
version,
username: 'Boat' + random,
offline: true
})

View file

@ -48,7 +48,7 @@ async function download (os, version, path = 'bds-') {
get(found, 'bds.zip')
console.info('⚡ Unzipping')
// Unzip server
if (process.platform === 'linux') cp.execSync('unzip bds.zip')
if (process.platform === 'linux') cp.execSync('unzip bds.zip && chmod +777 ./bedrock_server')
else cp.execSync('tar -xf bds.zip')
return verStr
}
@ -83,7 +83,11 @@ async function startServer (version, onStart, options = {}) {
configure(options)
const handle = run(!onStart)
if (onStart) {
handle.stdout.on('data', data => data.includes('Server started.') ? onStart() : null)
let stdout = ''
handle.stdout.on('data', data => {
stdout += data
if (stdout.includes('Server started')) onStart()
})
handle.stdout.pipe(process.stdout)
handle.stderr.pipe(process.stdout)
}

View file

@ -11,26 +11,52 @@ module.exports = (version) =>
}
static fromBedrock (obj) {
return new Item({
runtimeId: obj.runtime_id,
networkId: obj.item?.network_id,
count: obj.item?.auxiliary_value & 0xff,
metadata: obj.item?.auxiliary_value >> 8,
nbt: obj.item?.nbt?.nbt
})
if (version === '1.16.220') {
return new Item({
networkId: obj.network_id,
stackId: obj.stack_id,
blockRuntimeId: obj.block_runtime_id,
count: obj.count,
metadata: obj.metadata,
nbt: obj.extra.nbt
})
} else {
return new Item({
networkId: obj.runtime_id,
sackId: obj.item?.network_id,
count: obj.item?.auxiliary_value & 0xff,
metadata: obj.item?.auxiliary_value >> 8,
nbt: obj.item?.nbt?.nbt
})
}
}
toBedrock () {
return {
runtime_id: this.runtimeId,
item: {
if (version === '1.16.220') {
return {
network_id: this.networkId,
auxiliary_value: (this.metadata << 8) | (this.count & 0xff),
has_nbt: !!this.nbt,
nbt: { version: 1, nbt: this.nbt },
can_place_on: [],
can_destroy: [],
blocking_tick: 0
count: this.count,
metadata: this.metadata,
extra: {
has_nbt: !!this.nbt,
nbt: { version: 1, nbt: this.nbt },
can_place_on: [],
can_destroy: [],
blocking_tick: 0
}
}
} else {
return {
runtime_id: this.runtimeId,
item: {
network_id: this.networkId,
auxiliary_value: (this.metadata << 8) | (this.count & 0xff),
has_nbt: !!this.nbt,
nbt: { version: 1, nbt: this.nbt },
can_place_on: [],
can_destroy: [],
blocking_tick: 0
}
}
}
}