diff --git a/minecraft/conn.go b/minecraft/conn.go index 84bef5d1..ce1cbc39 100644 --- a/minecraft/conn.go +++ b/minecraft/conn.go @@ -697,7 +697,7 @@ func (conn *Conn) handleRequestNetworkSettings(pk *packet.RequestNetworkSettings } _ = conn.Flush() conn.enc.EnableCompression(conn.compression) - conn.dec.EnableCompression(conn.compression) + conn.dec.EnableCompression() return nil } @@ -708,7 +708,7 @@ func (conn *Conn) handleNetworkSettings(pk *packet.NetworkSettings) error { return fmt.Errorf("unknown compression algorithm: %v", pk.CompressionAlgorithm) } conn.enc.EnableCompression(alg) - conn.dec.EnableCompression(alg) + conn.dec.EnableCompression() conn.readyToLogin = true return nil } diff --git a/minecraft/protocol/info.go b/minecraft/protocol/info.go index 7b091441..90fdcf4f 100644 --- a/minecraft/protocol/info.go +++ b/minecraft/protocol/info.go @@ -2,7 +2,7 @@ package protocol const ( // CurrentProtocol is the current protocol version for the version below. - CurrentProtocol = 630 + CurrentProtocol = 649 // CurrentVersion is the current version of Minecraft as supported by the `packet` package. - CurrentVersion = "1.20.50" + CurrentVersion = "1.20.60" ) diff --git a/minecraft/protocol/packet/add_entity.go b/minecraft/protocol/packet/add_entity.go deleted file mode 100644 index 041a94e6..00000000 --- a/minecraft/protocol/packet/add_entity.go +++ /dev/null @@ -1,21 +0,0 @@ -package packet - -import ( - "github.com/sandertv/gophertunnel/minecraft/protocol" -) - -// AddEntity is sent by the server to the client. Its function is not entirely clear: It does not add an -// entity in the sense of an in-game entity, but has to do with the ECS that Minecraft uses. -type AddEntity struct { - // EntityNetworkID is the network ID of the entity that should be added. - EntityNetworkID uint64 -} - -// ID ... -func (pk *AddEntity) ID() uint32 { - return IDAddEntity -} - -func (pk *AddEntity) Marshal(io protocol.IO) { - io.Varuint64(&pk.EntityNetworkID) -} diff --git a/minecraft/protocol/packet/decoder.go b/minecraft/protocol/packet/decoder.go index 5dd0e08e..458a93c9 100644 --- a/minecraft/protocol/packet/decoder.go +++ b/minecraft/protocol/packet/decoder.go @@ -21,8 +21,8 @@ type Decoder struct { // NewDecoder implements the packetReader interface. pr packetReader - compression Compression - encrypt *encrypt + decompress bool + encrypt *encrypt checkPacketLimit bool } @@ -56,8 +56,8 @@ func (decoder *Decoder) EnableEncryption(keyBytes [32]byte) { } // EnableCompression enables compression for the Decoder. -func (decoder *Decoder) EnableCompression(compression Compression) { - decoder.compression = compression +func (decoder *Decoder) EnableCompression() { + decoder.decompress = true } // DisableBatchPacketLimit disables the check that limits the number of packets allowed in a single packet @@ -104,10 +104,18 @@ func (decoder *Decoder) Decode() (packets [][]byte, err error) { data = data[:len(data)-8] } - if decoder.compression != nil { - data, err = decoder.compression.Decompress(data) - if err != nil { - return nil, fmt.Errorf("error decompressing packet: %v", err) + if decoder.decompress { + if data[0] == 0xff { + data = data[1:] + } else { + compression, ok := CompressionByID(uint16(data[0])) + if !ok { + return nil, fmt.Errorf("error decompressing packet: unknown compression algorithm %v", data[0]) + } + data, err = compression.Decompress(data[1:]) + if err != nil { + return nil, fmt.Errorf("error decompressing packet: %v", err) + } } } diff --git a/minecraft/protocol/packet/encoder.go b/minecraft/protocol/packet/encoder.go index d40d70cc..16e7cbba 100644 --- a/minecraft/protocol/packet/encoder.go +++ b/minecraft/protocol/packet/encoder.go @@ -62,7 +62,9 @@ func (encoder *Encoder) Encode(packets [][]byte) error { } data := buf.Bytes() + prepend := []byte{header} if encoder.compression != nil { + prepend = append(prepend, byte(encoder.compression.EncodeCompression())) var err error data, err = encoder.compression.Compress(data) if err != nil { @@ -70,7 +72,7 @@ func (encoder *Encoder) Encode(packets [][]byte) error { } } - data = append([]byte{header}, data...) + data = append(prepend, data...) if encoder.encrypt != nil { // If the encryption session is not nil, encryption is enabled, meaning we should encrypt the // compressed data of this packet. diff --git a/minecraft/protocol/packet/id.go b/minecraft/protocol/packet/id.go index e24abccc..0548215e 100644 --- a/minecraft/protocol/packet/id.go +++ b/minecraft/protocol/packet/id.go @@ -127,8 +127,8 @@ const ( IDLevelEventGeneric IDLecternUpdate _ - IDAddEntity - IDRemoveEntity + _ + _ IDClientCacheStatus IDMapCreateLockedCopy IDOnScreenTextureAnimation @@ -208,4 +208,5 @@ const ( IDRefreshEntitlements IDPlayerToggleCrafterSlotRequest IDSetPlayerInventoryOptions + IDSetHud ) diff --git a/minecraft/protocol/packet/level_chunk.go b/minecraft/protocol/packet/level_chunk.go index 0da79315..50ca3075 100644 --- a/minecraft/protocol/packet/level_chunk.go +++ b/minecraft/protocol/packet/level_chunk.go @@ -11,6 +11,9 @@ type LevelChunk struct { // Position contains the X and Z coordinates of the chunk sent. You can convert a block coordinate to a chunk // coordinate by right-shifting it four bits. Position protocol.ChunkPos + // Dimension is the ID of the dimension that the chunk belongs to. This must always be set otherwise the + // client will always assume the chunk is part of the overworld dimension. + Dimension int32 // HighestSubChunk is the highest sub-chunk at the position that is not all air. It is only set if the // SubChunkCount is set to protocol.SubChunkRequestModeLimited. HighestSubChunk uint16 @@ -46,6 +49,7 @@ func (*LevelChunk) ID() uint32 { func (pk *LevelChunk) Marshal(io protocol.IO) { io.ChunkPos(&pk.Position) + io.Varint32(&pk.Dimension) io.Varuint32(&pk.SubChunkCount) if pk.SubChunkCount == protocol.SubChunkRequestModeLimited { io.Uint16(&pk.HighestSubChunk) diff --git a/minecraft/protocol/packet/level_event.go b/minecraft/protocol/packet/level_event.go index 4b02bfaa..53751283 100644 --- a/minecraft/protocol/packet/level_event.go +++ b/minecraft/protocol/packet/level_event.go @@ -7,123 +7,127 @@ import ( // noinspection SpellCheckingInspection const ( - LevelEventSoundClick = 1000 - LevelEventSoundClickFail = 1001 - LevelEventSoundLaunch = 1002 - LevelEventSoundOpenDoor = 1003 - LevelEventSoundFizz = 1004 - LevelEventSoundFuse = 1005 - LevelEventSoundPlayRecording = 1006 - LevelEventSoundGhastWarning = 1007 - LevelEventSoundGhastFireball = 1008 - LevelEventSoundBlazeFireball = 1009 - LevelEventSoundZombieWoodenDoor = 1010 - LevelEventSoundZombieDoorCrash = 1012 - LevelEventSoundZombieInfected = 1016 - LevelEventSoundZombieConverted = 1017 - LevelEventSoundEndermanTeleport = 1018 - LevelEventSoundAnvilBroken = 1020 - LevelEventSoundAnvilUsed = 1021 - LevelEventSoundAnvilLand = 1022 - LevelEventSoundInfinityArrowPickup = 1030 - LevelEventSoundTeleportEnderPearl = 1032 - LevelEventSoundAddItem = 1040 - LevelEventSoundItemFrameBreak = 1041 - LevelEventSoundItemFramePlace = 1042 - LevelEventSoundItemFrameRemoveItem = 1043 - LevelEventSoundItemFrameRotateItem = 1044 - LevelEventSoundExperienceOrbPickup = 1051 - LevelEventSoundTotemUsed = 1052 - LevelEventSoundArmorStandBreak = 1060 - LevelEventSoundArmorStandHit = 1061 - LevelEventSoundArmorStandLand = 1062 - LevelEventSoundArmorStandPlace = 1063 - LevelEventSoundPointedDripstoneLand = 1064 - LevelEventSoundDyeUsed = 1065 - LevelEventSoundInkSacUsed = 1066 - LevelEventSoundAmethystResonate = 1067 - LevelEventQueueCustomMusic = 1900 - LevelEventPlayCustomMusic = 1901 - LevelEventStopCustomMusic = 1902 - LevelEventSetMusicVolume = 1903 - LevelEventParticlesShoot = 2000 - LevelEventParticlesDestroyBlock = 2001 - LevelEventParticlesPotionSplash = 2002 - LevelEventParticlesEyeOfEnderDeath = 2003 - LevelEventParticlesMobBlockSpawn = 2004 - LevelEventParticleCropGrowth = 2005 - LevelEventParticleSoundGuardianGhost = 2006 - LevelEventParticleDeathSmoke = 2007 - LevelEventParticleDenyBlock = 2008 - LevelEventParticleGenericSpawn = 2009 - LevelEventParticlesDragonEgg = 2010 - LevelEventParticlesCropEaten = 2011 - LevelEventParticlesCritical = 2012 - LevelEventParticlesTeleport = 2013 - LevelEventParticlesCrackBlock = 2014 - LevelEventParticlesBubble = 2015 - LevelEventParticlesEvaporate = 2016 - LevelEventParticlesDestroyArmorStand = 2017 - LevelEventParticlesBreakingEgg = 2018 - LevelEventParticleDestroyEgg = 2019 - LevelEventParticlesEvaporateWater = 2020 - LevelEventParticlesDestroyBlockNoSound = 2021 - LevelEventParticlesKnockbackRoar = 2022 - LevelEventParticlesTeleportTrail = 2023 - LevelEventParticlesPointCloud = 2024 - LevelEventParticlesExplosion = 2025 - LevelEventParticlesBlockExplosion = 2026 - LevelEventParticlesVibrationSignal = 2027 - LevelEventParticlesDripstoneDrip = 2028 - LevelEventParticlesFizzEffect = 2029 - LevelEventWaxOn = 2030 - LevelEventWaxOff = 2031 - LevelEventScrape = 2032 - LevelEventParticlesElectricSpark = 2033 - LevelEventParticleTurtleEgg = 2034 - LevelEventParticleSculkShriek = 2035 - LevelEventSculkCatalystBloom = 2036 - LevelEventSculkCharge = 2037 - LevelEventSculkChargePop = 2038 - LevelEventSonicExplosion = 2039 - LevelEventDustPlume = 2040 - LevelEventStartRaining = 3001 - LevelEventStartThunderstorm = 3002 - LevelEventStopRaining = 3003 - LevelEventStopThunderstorm = 3004 - LevelEventGlobalPause = 3005 - LevelEventSimTimeStep = 3006 - LevelEventSimTimeScale = 3007 - LevelEventActivateBlock = 3500 - LevelEventCauldronExplode = 3501 - LevelEventCauldronDyeArmor = 3502 - LevelEventCauldronCleanArmor = 3503 - LevelEventCauldronFillPotion = 3504 - LevelEventCauldronTakePotion = 3505 - LevelEventCauldronFillWater = 3506 - LevelEventCauldronTakeWater = 3507 - LevelEventCauldronAddDye = 3508 - LevelEventCauldronCleanBanner = 3509 - LevelEventCauldronFlush = 3510 - LevelEventAgentSpawnEffect = 3511 - LevelEventCauldronFillLava = 3512 - LevelEventCauldronTakeLava = 3513 - LevelEventCauldronFillPowderSnow = 3514 - LevelEventCauldronTakePowderSnow = 3515 - LevelEventStartBlockCracking = 3600 - LevelEventStopBlockCracking = 3601 - LevelEventUpdateBlockCracking = 3602 - LevelEventParticlesCrackBlockDown = 3603 - LevelEventParticlesCrackBlockUp = 3604 - LevelEventParticlesCrackBlockNorth = 3605 - LevelEventParticlesCrackBlockSouth = 3606 - LevelEventParticlesCrackBlockWest = 3607 - LevelEventParticlesCrackBlockEast = 3608 - LevelEventParticlesShootWhiteSmoke = 3609 - LevelEventAllPlayersSleeping = 9800 - LevelEventSleepingPlayers = 9801 - LevelEventJumpPrevented = 9810 - LevelEventParticleLegacyEvent = 0x4000 + LevelEventSoundClick = 1000 + LevelEventSoundClickFail = 1001 + LevelEventSoundLaunch = 1002 + LevelEventSoundOpenDoor = 1003 + LevelEventSoundFizz = 1004 + LevelEventSoundFuse = 1005 + LevelEventSoundPlayRecording = 1006 + LevelEventSoundGhastWarning = 1007 + LevelEventSoundGhastFireball = 1008 + LevelEventSoundBlazeFireball = 1009 + LevelEventSoundZombieWoodenDoor = 1010 + LevelEventSoundZombieDoorCrash = 1012 + LevelEventSoundZombieInfected = 1016 + LevelEventSoundZombieConverted = 1017 + LevelEventSoundEndermanTeleport = 1018 + LevelEventSoundAnvilBroken = 1020 + LevelEventSoundAnvilUsed = 1021 + LevelEventSoundAnvilLand = 1022 + LevelEventSoundInfinityArrowPickup = 1030 + LevelEventSoundTeleportEnderPearl = 1032 + LevelEventSoundAddItem = 1040 + LevelEventSoundItemFrameBreak = 1041 + LevelEventSoundItemFramePlace = 1042 + LevelEventSoundItemFrameRemoveItem = 1043 + LevelEventSoundItemFrameRotateItem = 1044 + LevelEventSoundExperienceOrbPickup = 1051 + LevelEventSoundTotemUsed = 1052 + LevelEventSoundArmorStandBreak = 1060 + LevelEventSoundArmorStandHit = 1061 + LevelEventSoundArmorStandLand = 1062 + LevelEventSoundArmorStandPlace = 1063 + LevelEventSoundPointedDripstoneLand = 1064 + LevelEventSoundDyeUsed = 1065 + LevelEventSoundInkSacUsed = 1066 + LevelEventSoundAmethystResonate = 1067 + LevelEventQueueCustomMusic = 1900 + LevelEventPlayCustomMusic = 1901 + LevelEventStopCustomMusic = 1902 + LevelEventSetMusicVolume = 1903 + LevelEventParticlesShoot = 2000 + LevelEventParticlesDestroyBlock = 2001 + LevelEventParticlesPotionSplash = 2002 + LevelEventParticlesEyeOfEnderDeath = 2003 + LevelEventParticlesMobBlockSpawn = 2004 + LevelEventParticleCropGrowth = 2005 + LevelEventParticleSoundGuardianGhost = 2006 + LevelEventParticleDeathSmoke = 2007 + LevelEventParticleDenyBlock = 2008 + LevelEventParticleGenericSpawn = 2009 + LevelEventParticlesDragonEgg = 2010 + LevelEventParticlesCropEaten = 2011 + LevelEventParticlesCritical = 2012 + LevelEventParticlesTeleport = 2013 + LevelEventParticlesCrackBlock = 2014 + LevelEventParticlesBubble = 2015 + LevelEventParticlesEvaporate = 2016 + LevelEventParticlesDestroyArmorStand = 2017 + LevelEventParticlesBreakingEgg = 2018 + LevelEventParticleDestroyEgg = 2019 + LevelEventParticlesEvaporateWater = 2020 + LevelEventParticlesDestroyBlockNoSound = 2021 + LevelEventParticlesKnockbackRoar = 2022 + LevelEventParticlesTeleportTrail = 2023 + LevelEventParticlesPointCloud = 2024 + LevelEventParticlesExplosion = 2025 + LevelEventParticlesBlockExplosion = 2026 + LevelEventParticlesVibrationSignal = 2027 + LevelEventParticlesDripstoneDrip = 2028 + LevelEventParticlesFizzEffect = 2029 + LevelEventWaxOn = 2030 + LevelEventWaxOff = 2031 + LevelEventScrape = 2032 + LevelEventParticlesElectricSpark = 2033 + LevelEventParticleTurtleEgg = 2034 + LevelEventParticleSculkShriek = 2035 + LevelEventSculkCatalystBloom = 2036 + LevelEventSculkCharge = 2037 + LevelEventSculkChargePop = 2038 + LevelEventSonicExplosion = 2039 + LevelEventDustPlume = 2040 + LevelEventStartRaining = 3001 + LevelEventStartThunderstorm = 3002 + LevelEventStopRaining = 3003 + LevelEventStopThunderstorm = 3004 + LevelEventGlobalPause = 3005 + LevelEventSimTimeStep = 3006 + LevelEventSimTimeScale = 3007 + LevelEventActivateBlock = 3500 + LevelEventCauldronExplode = 3501 + LevelEventCauldronDyeArmor = 3502 + LevelEventCauldronCleanArmor = 3503 + LevelEventCauldronFillPotion = 3504 + LevelEventCauldronTakePotion = 3505 + LevelEventCauldronFillWater = 3506 + LevelEventCauldronTakeWater = 3507 + LevelEventCauldronAddDye = 3508 + LevelEventCauldronCleanBanner = 3509 + LevelEventCauldronFlush = 3510 + LevelEventAgentSpawnEffect = 3511 + LevelEventCauldronFillLava = 3512 + LevelEventCauldronTakeLava = 3513 + LevelEventCauldronFillPowderSnow = 3514 + LevelEventCauldronTakePowderSnow = 3515 + LevelEventStartBlockCracking = 3600 + LevelEventStopBlockCracking = 3601 + LevelEventUpdateBlockCracking = 3602 + LevelEventParticlesCrackBlockDown = 3603 + LevelEventParticlesCrackBlockUp = 3604 + LevelEventParticlesCrackBlockNorth = 3605 + LevelEventParticlesCrackBlockSouth = 3606 + LevelEventParticlesCrackBlockWest = 3607 + LevelEventParticlesCrackBlockEast = 3608 + LevelEventParticlesShootWhiteSmoke = 3609 + LevelEventParticlesWindExplosion = 3610 + LevelEventParticlesTrialSpawnerDetection = 3611 + LevelEventParticlesTrialSpawnerSpawning = 3612 + LevelEventParticlesTrialSpawnerEjecting = 3613 + LevelEventAllPlayersSleeping = 9800 + LevelEventSleepingPlayers = 9801 + LevelEventJumpPrevented = 9810 + LevelEventParticleLegacyEvent = 0x4000 ) // LevelEvent is sent by the server to make a certain event in the level occur. It ranges from particles, to diff --git a/minecraft/protocol/packet/player_auth_input.go b/minecraft/protocol/packet/player_auth_input.go index 9aae7b48..7a2576b4 100644 --- a/minecraft/protocol/packet/player_auth_input.go +++ b/minecraft/protocol/packet/player_auth_input.go @@ -51,6 +51,7 @@ const ( InputFlagStartFlying InputFlagStopFlying InputFlagClientAckServerData + InputFlagClientPredictedVehicle ) const ( @@ -120,8 +121,10 @@ type PlayerAuthInput struct { ItemStackRequest protocol.ItemStackRequest // BlockActions is a slice of block actions that the client has interacted with. BlockActions []protocol.PlayerBlockAction - // AnalogueMoveVector is a Vec2 that specifies the direction in which the player moved, as a combination of X/Z - // values which are created using an analogue input. + // ClientPredictedVehicle is the unique ID of the vehicle that the client predicts the player to be in. + ClientPredictedVehicle int64 + // AnalogueMoveVector is a Vec2 that specifies the direction in which the player moved, as a combination + // of X/Z values which are created using an analogue input. AnalogueMoveVector mgl32.Vec2 } @@ -154,6 +157,10 @@ func (pk *PlayerAuthInput) Marshal(io protocol.IO) { protocol.Single(io, &pk.ItemStackRequest) } + if pk.InputData&InputFlagClientPredictedVehicle != 0 { + io.Varint64(&pk.ClientPredictedVehicle) + } + if pk.InputData&InputFlagPerformBlockActions != 0 { protocol.SliceVarint32Length(io, &pk.BlockActions) } diff --git a/minecraft/protocol/packet/pool.go b/minecraft/protocol/packet/pool.go index 7351a36c..3dbd3c11 100644 --- a/minecraft/protocol/packet/pool.go +++ b/minecraft/protocol/packet/pool.go @@ -170,8 +170,6 @@ func init() { IDLevelEventGeneric: func() Packet { return &LevelEventGeneric{} }, IDLecternUpdate: func() Packet { return &LecternUpdate{} }, // --- - IDAddEntity: func() Packet { return &AddEntity{} }, - IDRemoveEntity: func() Packet { return &RemoveEntity{} }, IDClientCacheStatus: func() Packet { return &ClientCacheStatus{} }, IDOnScreenTextureAnimation: func() Packet { return &OnScreenTextureAnimation{} }, IDMapCreateLockedCopy: func() Packet { return &MapCreateLockedCopy{} }, @@ -252,6 +250,7 @@ func init() { IDRefreshEntitlements: func() Packet { return &RefreshEntitlements{} }, IDPlayerToggleCrafterSlotRequest: func() Packet { return &PlayerToggleCrafterSlotRequest{} }, IDSetPlayerInventoryOptions: func() Packet { return &SetPlayerInventoryOptions{} }, + IDSetHud: func() Packet { return &SetHud{} }, } for id, pk := range serverOriginating { RegisterPacketFromServer(id, pk) diff --git a/minecraft/protocol/packet/remove_entity.go b/minecraft/protocol/packet/remove_entity.go deleted file mode 100644 index 012587ce..00000000 --- a/minecraft/protocol/packet/remove_entity.go +++ /dev/null @@ -1,21 +0,0 @@ -package packet - -import ( - "github.com/sandertv/gophertunnel/minecraft/protocol" -) - -// RemoveEntity is sent by the server to the client. Its function is not entirely clear: It does not remove an -// entity in the sense of an in-game entity, but has to do with the ECS that Minecraft uses. -type RemoveEntity struct { - // EntityNetworkID is the network ID of the entity that should be removed. - EntityNetworkID uint64 -} - -// ID ... -func (pk *RemoveEntity) ID() uint32 { - return IDRemoveEntity -} - -func (pk *RemoveEntity) Marshal(io protocol.IO) { - io.Varuint64(&pk.EntityNetworkID) -} diff --git a/minecraft/protocol/packet/set_hud.go b/minecraft/protocol/packet/set_hud.go new file mode 100644 index 00000000..8118f257 --- /dev/null +++ b/minecraft/protocol/packet/set_hud.go @@ -0,0 +1,47 @@ +package packet + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" +) + +const ( + HudElementPaperDoll = iota + HudElementArmour + HudElementToolTips + HudElementTouchControls + HudElementCrosshair + HudElementHotBar + HudElementHealth + HudElementProgressBar + HudElementHunger + HudElementAirBubbles + HudElementHourseHealth +) + +const ( + HudVisibilityHide = iota + HudVisibilityReset +) + +// SetHud is sent by the server to set the visibility of individual HUD elements on the client. It is +// important to note that the client does not reset the state of the HUD elements after it leaves a server, +// meaning they can leak into sessions on different servers. To be safe, you should reset the visibility of +// all HUD elements when a player connects. +type SetHud struct { + // Elements is a list of HUD elements that are being modified. The values can be any of the HudElement + // constants above. + Elements []byte + // Visibility represents the new visibility of the specified Elements. It can be any of the HudVisibility + // constants above. + Visibility byte +} + +// ID ... +func (*SetHud) ID() uint32 { + return IDSetHud +} + +func (pk *SetHud) Marshal(io protocol.IO) { + protocol.FuncSlice(io, &pk.Elements, io.Uint8) + io.Uint8(&pk.Visibility) +} diff --git a/minecraft/protocol/player.go b/minecraft/protocol/player.go index 0871c53f..69633e7b 100644 --- a/minecraft/protocol/player.go +++ b/minecraft/protocol/player.go @@ -79,6 +79,8 @@ type PlayerListEntry struct { Teacher bool // Host specifies if the player that is added to the player list is the host of the game. Host bool + // SubClient specifies if the player that is added to the player list is a sub-client of another player. + SubClient bool } // Marshal encodes/decodes a PlayerListEntry. @@ -92,6 +94,7 @@ func (x *PlayerListEntry) Marshal(r IO) { Single(r, &x.Skin) r.Bool(&x.Teacher) r.Bool(&x.Host) + r.Bool(&x.SubClient) } // PlayerListRemoveEntry encodes/decodes a PlayerListEntry for removal from the list.