diff --git a/src/main/java/config/Version.java b/src/main/java/config/Version.java index 8723162c..5f5eec4d 100644 --- a/src/main/java/config/Version.java +++ b/src/main/java/config/Version.java @@ -12,6 +12,7 @@ public enum Version { V1_19(759, 3105), V1_19_3(761, 3218), V1_20(763, 3463), + V1_20_2(764, 3578), ANY(0, 0); public final int dataVersion; diff --git a/src/main/java/game/data/chunk/ChunkFactory.java b/src/main/java/game/data/chunk/ChunkFactory.java index fea95978..a821b526 100644 --- a/src/main/java/game/data/chunk/ChunkFactory.java +++ b/src/main/java/game/data/chunk/ChunkFactory.java @@ -45,6 +45,7 @@ public void clear() { */ private static Chunk getVersionedChunk(int dataVersion, CoordinateDim2D chunkPos) { return VersionReporter.select(dataVersion, Chunk.class, + Option.of(Version.V1_20_2, () -> new Chunk_1_20_2(chunkPos, dataVersion)), Option.of(Version.V1_20, () -> new Chunk_1_20(chunkPos, dataVersion)), Option.of(Version.V1_18, () -> new Chunk_1_18(chunkPos, dataVersion)), Option.of(Version.V1_17, () -> new Chunk_1_17(chunkPos, dataVersion)), diff --git a/src/main/java/game/data/chunk/version/Chunk_1_18.java b/src/main/java/game/data/chunk/version/Chunk_1_18.java index 073b2adb..fa55e2b8 100644 --- a/src/main/java/game/data/chunk/version/Chunk_1_18.java +++ b/src/main/java/game/data/chunk/version/Chunk_1_18.java @@ -133,7 +133,7 @@ public void readChunkColumn(DataTypeProvider dataProvider) { } } - private void findBlockEntities(ChunkSection section, int sectionY) { + protected void findBlockEntities(ChunkSection section, int sectionY) { BlockEntityRegistry blockEntities = RegistryManager.getInstance().getBlockEntityRegistry(); GlobalPalette globalPalette = GlobalPaletteProvider.getGlobalPalette(getDataVersion()); @@ -151,7 +151,7 @@ private void findBlockEntities(ChunkSection section, int sectionY) { } } - private boolean containsBlockEntities(Palette p) { + protected boolean containsBlockEntities(Palette p) { BlockEntityRegistry blockEntities = RegistryManager.getInstance().getBlockEntityRegistry(); for (SpecificTag tag : p.toNbt()) { if (blockEntities.isBlockEntity(tag.get("Name").stringValue())) { diff --git a/src/main/java/game/data/chunk/version/Chunk_1_20_2.java b/src/main/java/game/data/chunk/version/Chunk_1_20_2.java new file mode 100644 index 00000000..6c3c4319 --- /dev/null +++ b/src/main/java/game/data/chunk/version/Chunk_1_20_2.java @@ -0,0 +1,51 @@ +package game.data.chunk.version; + +import game.data.chunk.palette.Palette; +import game.data.chunk.palette.PaletteType; +import game.data.coordinates.CoordinateDim2D; +import packets.DataTypeProvider; + +public class Chunk_1_20_2 extends Chunk_1_20 { + + + public Chunk_1_20_2(CoordinateDim2D location, int version) { + super(location, version); + } + + /** + * Read a chunk column for 1.20.2 + */ + public void readChunkColumn(DataTypeProvider dataProvider) { + // Loop through section Y values, starting from the lowest section that has blocks inside it. + for (int sectionY = getMinBlockSection(); sectionY <= getMaxBlockSection() && dataProvider.hasNext(); sectionY++) { + ChunkSection_1_18 section = (ChunkSection_1_18) getChunkSection(sectionY); + + dataProvider.readShort(); + Palette blockPalette = Palette.readPalette(dataProvider, PaletteType.BLOCKS); + + if (section == null) { + section = (ChunkSection_1_18) createNewChunkSection((byte) (sectionY & 0xFF), blockPalette); + } else { + section.setBlockPalette(blockPalette); + } + + section.setBlocks(dataProvider.readLongArray(dataProvider.readVarInt())); + + Palette biomePalette = Palette.readPalette(dataProvider, PaletteType.BIOMES); + section.setBiomePalette(biomePalette); + + // check how many longs we expect, if there's more discard the rest + int longsExpectedBiomes = ChunkSection_1_18.longsRequiredBiomes(biomePalette.getBitsPerBlock()); + section.setBiomes(dataProvider.readLongArray(dataProvider.readVarInt())); + + // May replace an existing section or a null one + setChunkSection(sectionY, section); + + // servers don't (always?) include containers in the list of block_entities. We need to know that these block + // entities exist, otherwise we'll end up not writing block entity data for them + if (containsBlockEntities(blockPalette)) { + findBlockEntities(section, sectionY); + } + } + } +} diff --git a/src/main/java/game/data/dimension/DimensionCodec.java b/src/main/java/game/data/dimension/DimensionCodec.java index b0ea1e7b..a38da1bf 100644 --- a/src/main/java/game/data/dimension/DimensionCodec.java +++ b/src/main/java/game/data/dimension/DimensionCodec.java @@ -63,16 +63,20 @@ private DimensionCodec() { this.biomes = new HashMap<>(); } - public static DimensionCodec fromNbt(String[] dimensionNames, SpecificTag tag) { + public static DimensionCodec fromNbt(SpecificTag tag) { DimensionCodec codec = new DimensionCodec(); - codec.readDimensions(dimensionNames); codec.readDimensionTypes(tag.get("minecraft:dimension_type").asCompound().get("value").asList()); codec.readBiomes(tag.get("minecraft:worldgen/biome").asCompound().get("value").asList()); return codec; } + public DimensionCodec setDimensionNames(String[] dimensionNames) { + this.readDimensions(dimensionNames); + return this; + } + public Collection getDimensions() { return dimensions.values(); } diff --git a/src/main/java/game/data/entity/EntityRegistry.java b/src/main/java/game/data/entity/EntityRegistry.java index 47f397eb..873e48c4 100644 --- a/src/main/java/game/data/entity/EntityRegistry.java +++ b/src/main/java/game/data/entity/EntityRegistry.java @@ -2,11 +2,7 @@ import static util.ExceptionHandling.attempt; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -18,11 +14,12 @@ import game.data.coordinates.CoordinateDim2D; import game.data.entity.specific.Villager; import packets.DataTypeProvider; +import packets.UUID; import se.llbit.nbt.SpecificTag; public class EntityRegistry { - private final Map players; + private final Map players; private final Map> perChunk; private final Map entities; private final WorldManager worldManager; @@ -73,7 +70,6 @@ public void addEntity(DataTypeProvider provider, Function markUnsaved(pos.globalToDimChunk())); } })); @@ -81,8 +77,64 @@ public void addEntity(DataTypeProvider provider, Function attempt(() -> { - int entId = provider.readVarInt(); - players.put(entId, PlayerEntity.parse(provider)); + PlayerEntity player = PlayerEntity.parse(provider); + players.put(player.getUUID(), player); + })); + } + + public void updatePlayerAction(DataTypeProvider provider) { + executor.execute(() -> attempt(() -> { + byte actions = provider.readNext(); + int playerCnt = provider.readVarInt(); + + for (int i = 0; i < playerCnt; i++) { + UUID uuid = provider.readUUID(); + + if ((actions & 0x01) > 0) { + PlayerEntity player = new PlayerEntity(uuid); + players.put(uuid, player); + + String name = provider.readString(); + int properties = provider.readVarInt(); + for (int j = 0; j < properties; j++) { + provider.readString(); + provider.readString(); + boolean signed = provider.readBoolean(); + if (signed) provider.readString(); + } + } + + if ((actions & 0x02) > 0) { + boolean signature = provider.readBoolean(); + if (signature) { + provider.readUUID(); + provider.readLong(); + int encKeySz = provider.readVarInt(); + provider.readByteArray(encKeySz); + int pubKeySz = provider.readVarInt(); + provider.readByteArray(pubKeySz); + } + } + + if ((actions & 0x04) > 0) { + provider.readVarInt(); + } + + if ((actions & 0x08) > 0) { + provider.readBoolean(); + } + + if ((actions & 0x10) > 0) { + provider.readVarInt(); + } + + if ((actions & 0x20) > 0) { + boolean displayName = provider.readBoolean(); + if (displayName) { + provider.readChat(); + } + } + } })); } @@ -139,12 +191,14 @@ public void updatePositionAbsolute(DataTypeProvider provider) { } public IMovableEntity getMovableEntity(int entId) { - IMovableEntity ent = players.get(entId); + Entity tmpEnt = entities.get(entId); + if (tmpEnt == null) return null; + IMovableEntity ent = players.get(tmpEnt.uuid); if (ent != null) { return ent; } - return entities.get(entId); + return tmpEnt; } public List getEntitiesNbt(CoordinateDim2D location) { @@ -183,9 +237,8 @@ public void destroyEntities(DataTypeProvider provider) { while (count-- > 0) { int id = provider.readVarInt(); if (entities.containsKey(id)) { + players.remove(entities.get(id).uuid); entities.remove(id); - } else { - players.remove(id); } } } diff --git a/src/main/java/game/data/entity/PlayerEntity.java b/src/main/java/game/data/entity/PlayerEntity.java index 7d89b005..6103d8a6 100644 --- a/src/main/java/game/data/entity/PlayerEntity.java +++ b/src/main/java/game/data/entity/PlayerEntity.java @@ -17,9 +17,12 @@ public class PlayerEntity implements IMovableEntity { private boolean hasRequestedName = false; private String name; + PlayerEntity(UUID uuid) { + this.uuid = uuid; + } + public static PlayerEntity parse(DataTypeProvider provider) { - PlayerEntity ent = new PlayerEntity(); - ent.uuid = provider.readUUID(); + PlayerEntity ent = new PlayerEntity(provider.readUUID()); ent.readPosition(provider); return ent; @@ -86,4 +89,8 @@ public String getName() { } return name; } + + public UUID getUUID() { + return uuid; + } } diff --git a/src/main/java/packets/DataTypeProvider.java b/src/main/java/packets/DataTypeProvider.java index 24a411dc..2a5cd7d0 100644 --- a/src/main/java/packets/DataTypeProvider.java +++ b/src/main/java/packets/DataTypeProvider.java @@ -9,6 +9,7 @@ import game.data.coordinates.CoordinateDouble3D; import packets.version.DataTypeProvider_1_13; import packets.version.DataTypeProvider_1_14; +import packets.version.DataTypeProvider_1_20_2; import se.llbit.nbt.NamedTag; import se.llbit.nbt.SpecificTag; @@ -37,6 +38,7 @@ public DataTypeProvider(byte[] finalFullPacket) { public static DataTypeProvider ofPacket(byte[] finalFullPacket) { return Config.versionReporter().select(DataTypeProvider.class, + Option.of(Version.V1_20_2, () -> new DataTypeProvider_1_20_2(finalFullPacket)), Option.of(Version.V1_14, () -> new DataTypeProvider_1_14(finalFullPacket)), Option.of(Version.V1_13, () -> new DataTypeProvider_1_13(finalFullPacket)), Option.of(Version.ANY, () -> new DataTypeProvider(finalFullPacket)) @@ -223,7 +225,7 @@ public double readDouble() { public UUID readUUID() { return new UUID(readLong(), readLong()); } - + public UUID readOptUUID() { if (readBoolean()) { return readUUID(); diff --git a/src/main/java/packets/builder/PacketBuilder.java b/src/main/java/packets/builder/PacketBuilder.java index 53e31292..c7ba2c83 100644 --- a/src/main/java/packets/builder/PacketBuilder.java +++ b/src/main/java/packets/builder/PacketBuilder.java @@ -199,6 +199,7 @@ public void writeBoolean(boolean val) { /** * Writes an NBT tag. We need to wrap this in a NamedTag, as the named tag is not written itself. + * TODO: update on 1.20.2 */ public void writeNbt(SpecificTag nbt) { try { diff --git a/src/main/java/packets/handler/ClientBoundGamePacketHandler.java b/src/main/java/packets/handler/ClientBoundGamePacketHandler.java index 74542ab9..3474e6ae 100644 --- a/src/main/java/packets/handler/ClientBoundGamePacketHandler.java +++ b/src/main/java/packets/handler/ClientBoundGamePacketHandler.java @@ -13,12 +13,7 @@ import game.data.entity.EntityRegistry; import game.data.entity.MobEntity; import game.data.entity.ObjectEntity; -import packets.handler.version.ClientBoundGamePacketHandler_1_14; -import packets.handler.version.ClientBoundGamePacketHandler_1_15; -import packets.handler.version.ClientBoundGamePacketHandler_1_16; -import packets.handler.version.ClientBoundGamePacketHandler_1_17; -import packets.handler.version.ClientBoundGamePacketHandler_1_18; -import packets.handler.version.ClientBoundGamePacketHandler_1_19; +import packets.handler.version.*; import packets.handler.plugins.PluginChannelHandler; import proxy.ConnectionManager; import se.llbit.nbt.SpecificTag; @@ -184,6 +179,7 @@ public ClientBoundGamePacketHandler(ConnectionManager connectionManager) { public static PacketHandler of(ConnectionManager connectionManager) { return Config.versionReporter().select(PacketHandler.class, + Option.of(Version.V1_20_2, () -> new ClientBoundGamePacketHandler_1_20_2(connectionManager)), Option.of(Version.V1_19, () -> new ClientBoundGamePacketHandler_1_19(connectionManager)), Option.of(Version.V1_18, () -> new ClientBoundGamePacketHandler_1_18(connectionManager)), Option.of(Version.V1_17, () -> new ClientBoundGamePacketHandler_1_17(connectionManager)), diff --git a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java index 828c9ad3..a5426dd3 100644 --- a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java +++ b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java @@ -34,7 +34,7 @@ public ClientBoundGamePacketHandler_1_16(ConnectionManager connectionManager) { String[] dimensionNames = provider.readStringArray(numDimensions); SpecificTag dimensionCodec = provider.readNbtTag(); - WorldManager.getInstance().setDimensionCodec(DimensionCodec.fromNbt(dimensionNames, dimensionCodec)); + WorldManager.getInstance().setDimensionCodec(DimensionCodec.fromNbt(dimensionCodec).setDimensionNames(dimensionNames)); SpecificTag dimensionNbt = provider.readNbtTag(); diff --git a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java index 0a011f93..f264cfae 100644 --- a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java +++ b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java @@ -35,7 +35,7 @@ public ClientBoundGamePacketHandler_1_19(ConnectionManager connectionManager) { String[] dimensionNames = provider.readStringArray(numDimensions); SpecificTag dimensionCodec = provider.readNbtTag(); - WorldManager.getInstance().setDimensionCodec(DimensionCodec.fromNbt(dimensionNames, dimensionCodec)); + WorldManager.getInstance().setDimensionCodec(DimensionCodec.fromNbt(dimensionCodec).setDimensionNames(dimensionNames)); String dimensionType = provider.readString(); // current active dimension diff --git a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_2.java b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_2.java new file mode 100644 index 00000000..9b521468 --- /dev/null +++ b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_2.java @@ -0,0 +1,90 @@ +package packets.handler.version; + +import config.Config; +import game.data.WorldManager; +import game.data.dimension.Dimension; +import game.data.dimension.DimensionCodec; +import game.data.entity.EntityRegistry; +import game.protocol.Protocol; +import packets.builder.PacketBuilder; +import packets.handler.PacketOperator; +import proxy.ConnectionManager; +import se.llbit.nbt.CompoundTag; +import se.llbit.nbt.SpecificTag; + +import java.util.Map; + +import static packets.builder.NetworkType.*; + +public class ClientBoundGamePacketHandler_1_20_2 extends ClientBoundGamePacketHandler_1_19 { + public ClientBoundGamePacketHandler_1_20_2(ConnectionManager connectionManager) { + super(connectionManager); + + Protocol protocol = Config.versionReporter().getProtocol(); + WorldManager worldManager = WorldManager.getInstance(); + EntityRegistry entityRegistry = WorldManager.getInstance().getEntityRegistry(); + + Map operators = getOperators(); + operators.put("Login", provider -> { + PacketBuilder replacement = new PacketBuilder(protocol.clientBound("Login")); + + replacement.copy(provider, INT, BOOL); + + // handle dimension codec + int numDimensions = provider.readVarInt(); + String[] dimensionNames = provider.readStringArray(numDimensions); + WorldManager.getInstance().getDimensionCodec().setDimensionNames(dimensionNames); + + replacement.writeVarInt(numDimensions); + replacement.writeStringArray(dimensionNames); + + replacement.copy(provider, VARINT); + + // extend view distance communicated to the client to the given value + int viewDist = provider.readVarInt(); + replacement.writeVarInt(Math.max(viewDist, Config.getExtendedRenderDistance())); + + replacement.copy(provider, VARINT, BOOL, BOOL, BOOL); + + // current active dimension + String dimensionType = provider.readString(); + String dimensionName = provider.readString(); + Dimension dimension = Dimension.fromString(dimensionName); + dimension.setType(dimensionType); + WorldManager.getInstance().setDimension(dimension); + + replacement.writeString(dimensionType); + replacement.writeString(dimensionName); + + replacement.copy(provider, LONG, BYTE, BYTE, BOOL, BOOL); + + replacement.copyRemainder(provider); + + getConnectionManager().getEncryptionManager().sendImmediately(replacement); + return false; + }); + + operators.put("UpdatePlayerInfo", provider -> { + entityRegistry.updatePlayerAction(provider); + return true; + }); + + operators.put("RegistryData", provider -> { + try { + SpecificTag dimensionCodec = provider.readNbtTag(); + if (!(dimensionCodec instanceof CompoundTag)) { + return true; + } + for (var nbt : ((CompoundTag) dimensionCodec)) { + if (nbt.name.equals("minecraft:dimension_type")) { + worldManager.setDimensionCodec(DimensionCodec.fromNbt(dimensionCodec)); + break; + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return true; + }); + } +} \ No newline at end of file diff --git a/src/main/java/packets/version/DataTypeProvider_1_20_2.java b/src/main/java/packets/version/DataTypeProvider_1_20_2.java new file mode 100644 index 00000000..63213527 --- /dev/null +++ b/src/main/java/packets/version/DataTypeProvider_1_20_2.java @@ -0,0 +1,32 @@ +package packets.version; + +import packets.DataTypeProvider; +import se.llbit.nbt.SpecificTag; + +import java.io.DataInputStream; +import java.io.InputStream; + +public class DataTypeProvider_1_20_2 extends DataTypeProvider_1_14 { + public DataTypeProvider_1_20_2(byte[] finalFullPacket) { + super(finalFullPacket); + } + + public SpecificTag readNbtTag() { + try { + return (SpecificTag) SpecificTag.read(readNext(), new DataInputStream(new InputStream() { + @Override + public int read() { + return readNext() & 0xFF; + } + })).unpack(); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + @Override + public DataTypeProvider ofLength(int length) { + return new DataTypeProvider_1_20_2(this.readByteArray(length)); + } +} diff --git a/src/main/resources/protocol-versions.json b/src/main/resources/protocol-versions.json index 3349954d..ec0e444b 100644 --- a/src/main/resources/protocol-versions.json +++ b/src/main/resources/protocol-versions.json @@ -399,6 +399,47 @@ "0x31": "UseItemOn", "0x32": "UseItem" } + }, + "764": { + "version": "1.20.2", + "dataVersion": 3578, + "clientBound": { + "0x01": "AddEntity", + "0x05": "RegistryData", + "0x3c": "UpdatePlayerInfo", + "0x07": "BlockEntityData", + "0x09": "BlockUpdate", + "0x12": "ContainerClose", + "0x13": "ContainerSetContent", + "0x1f": "ForgetLevelChunk", + "0x25": "LevelChunkWithLight", + "0x28": "LightUpdate", + "0x29": "Login", + "0x2a": "MapItemData", + "0x2b": "TradeList", + "0x2c": "MoveEntityPos", + "0x2d": "MoveEntityPosRot", + "0x31": "OpenScreen", + "0x40": "RemoveEntities", + "0x43": "Respawn", + "0x45": "SectionBlocksUpdate", + "0x51": "SetChunkCacheRadius", + "0x54": "SetEntityData", + "0x57": "SetEquipment", + "0x67": "SystemChat", + "0x6b": "TeleportEntity" + }, + "serverBound": { + "0x0c": "ContainerClose", + "0x12": "Interact", + "0x16": "MovePlayerPos", + "0x17": "MovePlayerPosRot", + "0x18": "MovePlayerRot", + "0x1a": "MoveVehicle", + "0x2c": "SetCommandBlock", + "0x34": "UseItemOn", + "0x35": "UseItem" + } } } } \ No newline at end of file diff --git a/src/test/java/game/protocol/ProtocolVersionHandlerTest.java b/src/test/java/game/protocol/ProtocolVersionHandlerTest.java index 824200e1..3de7aa43 100644 --- a/src/test/java/game/protocol/ProtocolVersionHandlerTest.java +++ b/src/test/java/game/protocol/ProtocolVersionHandlerTest.java @@ -25,6 +25,7 @@ void bestMatch() { versions.put(758, "1.18"); versions.put(761, "1.19.3"); versions.put(763, "1.20"); + versions.put(764, "1.20.2"); versions.forEach((k, v) -> { assertThat(pvh.getProtocolByProtocolVersion(k).getVersion()).isEqualTo(v);