From fe9a4291bf2250db4334635ab58e2db3109a7e42 Mon Sep 17 00:00:00 2001 From: themode Date: Thu, 20 Oct 2022 03:53:01 +0200 Subject: [PATCH] hollow-cube/1.19.3 Signed-off-by: mworzala fix sounds v2 (cherry picked from commit e6d4a2cc919d6ab5aa9402cc871a70535069d803) fix command packet (cherry picked from commit f3efd64f7bd1d0473e0899d202268f77bab35abf) fix sound effect packets (cherry picked from commit 8530b37b354129a2149aafe2820183b28766be00) remove named sound effect & fix sound effect, entity sound effect packet is still wrong (cherry picked from commit 612f6065a12c465c07816c8551b1c44f17c21159) update NamedSoundEffectPacket to 1.19.3 (cherry picked from commit 8c78d9beac96f94770f6fd0e9216452c3421bcfd) update datagen, add read method to player info update (though it seems kinda broken) (cherry picked from commit 6464a72dabc5edaf9b09ef1b8100815965bbad74) Add ChatSession Signed-off-by: TheMode (cherry picked from commit 0488915fdaff4b51c4375da27be2dcf79215435d) Unnecessary line change Signed-off-by: TheMode (cherry picked from commit 915836f490fb3c28b3326edcc6da1de57fec118c) Make tests compile Signed-off-by: TheMode (cherry picked from commit 220217fcc1030441f1d2c3f6e96d3d4ca68331f3) Fix info update Signed-off-by: TheMode (cherry picked from commit 1a606285c0ceb1252ed505acd963430a3d8cb0a7) Fix unsigned message Signed-off-by: TheMode (cherry picked from commit 7ba55fdfef9f753c3ba6a1ca3d569b3c6fe4418e) 1.19.3 support (cherry picked from commit f09fdd862bd185f4f8bdff8fdfcd45945b38f4ab) --- demo/build.gradle.kts | 2 + .../src/main/java/net/minestom/demo/Main.java | 24 ++ .../minestom/demo/commands/TestCommand.java | 9 + gradle/libs.versions.toml | 2 +- .../net/minestom/server/MinecraftServer.java | 4 +- .../adventure/AdventurePacketConvertor.java | 15 +- .../registry/ArgumentPotionEffect.java | 3 +- .../net/minestom/server/crypto/ChatBound.java | 22 ++ .../minestom/server/crypto/ChatSession.java | 18 ++ .../minestom/server/crypto/FilterMask.java | 28 ++ .../server/crypto/LastSeenMessages.java | 28 +- .../server/crypto/MessageSignature.java | 35 ++- .../server/crypto/SignedMessageBody.java | 31 +++ .../minestom/server/entity/LivingEntity.java | 4 +- .../net/minestom/server/entity/Metadata.java | 39 +-- .../net/minestom/server/entity/Player.java | 42 +-- .../server/network/NetworkBuffer.java | 41 ++- .../packet/client/ClientPacketsHandler.java | 52 ++-- .../login/EncryptionResponsePacket.java | 25 +- .../packet/client/login/LoginStartPacket.java | 32 +-- .../client/play/ClientChatAckPacket.java | 9 +- .../client/play/ClientChatMessagePacket.java | 31 +-- .../client/play/ClientChatPreviewPacket.java | 26 -- .../play/ClientChatSessionUpdatePacket.java | 17 ++ .../client/play/ClientCommandChatPacket.java | 8 +- .../packet/server/ServerPacketIdentifier.java | 9 +- .../packet/server/play/ChatPreviewPacket.java | 42 --- .../server/play/EntitySoundEffectPacket.java | 73 +++++- .../packet/server/play/ExplosionPacket.java | 10 +- .../server/play/NamedSoundEffectPacket.java | 36 --- .../server/play/PlayerChatHeaderPacket.java | 29 --- .../server/play/PlayerChatMessagePacket.java | 49 ++-- .../packet/server/play/PlayerInfoPacket.java | 242 ------------------ .../server/play/PlayerInfoRemovePacket.java | 33 +++ .../server/play/PlayerInfoUpdatePacket.java | 154 +++++++++++ .../packet/server/play/ServerDataPacket.java | 5 +- .../server/play/SetChatPreviewPacket.java | 24 -- .../packet/server/play/SoundEffectPacket.java | 76 +++++- .../server/network/PacketWriteReadTest.java | 36 ++- 39 files changed, 736 insertions(+), 629 deletions(-) create mode 100644 src/main/java/net/minestom/server/crypto/ChatBound.java create mode 100644 src/main/java/net/minestom/server/crypto/ChatSession.java create mode 100644 src/main/java/net/minestom/server/crypto/FilterMask.java create mode 100644 src/main/java/net/minestom/server/crypto/SignedMessageBody.java delete mode 100644 src/main/java/net/minestom/server/network/packet/client/play/ClientChatPreviewPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/client/play/ClientChatSessionUpdatePacket.java delete mode 100644 src/main/java/net/minestom/server/network/packet/server/play/ChatPreviewPacket.java delete mode 100644 src/main/java/net/minestom/server/network/packet/server/play/NamedSoundEffectPacket.java delete mode 100644 src/main/java/net/minestom/server/network/packet/server/play/PlayerChatHeaderPacket.java delete mode 100644 src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoRemovePacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java delete mode 100644 src/main/java/net/minestom/server/network/packet/server/play/SetChatPreviewPacket.java diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 60b18683d..e84143ee7 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -17,6 +17,8 @@ application { dependencies { implementation(rootProject) implementation(libs.jNoise) + implementation("ch.qos.logback:logback-core:1.4.5") + implementation("ch.qos.logback:logback-classic:1.4.5") } tasks.withType { diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 4030fd3d7..a6447994e 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -1,5 +1,6 @@ package net.minestom.demo; +import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; @@ -8,6 +9,7 @@ import net.kyori.adventure.text.format.TextDecoration; import net.minestom.demo.commands.*; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; +import net.minestom.server.event.player.PlayerChatEvent; import net.minestom.server.event.server.ServerListPingEvent; import net.minestom.server.extras.lan.OpenToLAN; import net.minestom.server.extras.lan.OpenToLANConfig; @@ -15,6 +17,7 @@ import net.minestom.server.extras.optifine.OptifineSupport; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.rule.vanilla.RedstonePlacementRule; import net.minestom.server.ping.ResponseData; +import net.minestom.server.sound.SoundEvent; import net.minestom.server.utils.identity.NamedAndIdentified; import net.minestom.server.utils.time.TimeUnit; @@ -96,6 +99,27 @@ public class Main { //responseData.setPlayersHidden(true); }); + MinecraftServer.getGlobalEventHandler().addListener(PlayerChatEvent.class, e -> { + var playPos = e.getPlayer().getPosition().add(5.0, 0.0, 0.0); + + switch (e.getMessage()) { + case "a" -> { + System.out.println("with position"); + e.getPlayer().playSound(Sound.sound(SoundEvent.AMBIENT_CRIMSON_FOREST_MOOD, Sound.Source.MASTER, 1f, 1f), playPos.x(), playPos.y(), playPos.z()); + } + case "b" -> { + System.out.println("without anything"); + e.getPlayer().playSound(Sound.sound(SoundEvent.AMBIENT_CRIMSON_FOREST_MOOD, Sound.Source.MASTER, 1f, 1f)); + } + case "c" -> { + System.out.println("with self emitter"); + e.getPlayer().playSound(Sound.sound(SoundEvent.AMBIENT_CRIMSON_FOREST_MOOD, Sound.Source.MASTER, 1f, 1f), Sound.Emitter.self()); + } + } + + + }); + PlayerInit.init(); OptifineSupport.enable(); diff --git a/demo/src/main/java/net/minestom/demo/commands/TestCommand.java b/demo/src/main/java/net/minestom/demo/commands/TestCommand.java index c2294cb9b..75482a678 100644 --- a/demo/src/main/java/net/minestom/demo/commands/TestCommand.java +++ b/demo/src/main/java/net/minestom/demo/commands/TestCommand.java @@ -1,10 +1,14 @@ package net.minestom.demo.commands; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; +import net.minestom.server.adventure.AdventurePacketConvertor; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.sound.SoundEvent; public class TestCommand extends Command { @@ -15,7 +19,12 @@ public class TestCommand extends Command { var block = ArgumentType.BlockState("block"); block.setCallback((sender, exception) -> exception.printStackTrace()); + setDefaultExecutor((sender, context) -> { + sender.playSound(Sound.sound(Key.key("item.trumpet.doot"), Sound.Source.PLAYER, 1, 1)); + AdventurePacketConvertor.createSoundPacket(Sound.sound(Key.key(SoundEvent.BLOCK_ANVIL_HIT.name()), Sound.Source.HOSTILE, 1, 1), sender.asPlayer()); + }); addSyntax((sender, context) -> System.out.println("executed"), block); + } private void usage(CommandSender sender, CommandContext context) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2d34cb966..96861dcb0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ adventure = "4.12.0" kotlin = "1.7.22" hydrazine = "1.7.2" dependencyGetter = "v1.0.1" -minestomData = "1c1921cd41" +minestomData = "53e0da5be1" hephaistos = "2.5.3" jetbrainsAnnotations = "23.0.0" diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 077c6f728..059ebc19b 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -45,8 +45,8 @@ public final class MinecraftServer { public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class); - public static final String VERSION_NAME = "1.19.2"; - public static final int PROTOCOL_VERSION = 760; + public static final String VERSION_NAME = "1.19.3"; + public static final int PROTOCOL_VERSION = 761; // Threads public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark"; diff --git a/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java b/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java index d5ef48364..04244d43a 100644 --- a/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java +++ b/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java @@ -11,6 +11,7 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.title.Title; import net.kyori.adventure.title.TitlePart; import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.play.*; @@ -111,11 +112,11 @@ public class AdventurePacketConvertor { public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, double x, double y, double z) { final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString()); if (minestomSound == null) { - return new NamedSoundEffectPacket(sound.name().asString(), sound.source(), - (int) x, (int) y, (int) z, sound.volume(), sound.pitch(), 0); + return new SoundEffectPacket(sound.name().asString(), null, sound.source(), + new Vec(x, y, z), sound.volume(), sound.pitch(), 0); } else { - return new SoundEffectPacket(minestomSound.id(), sound.source(), - (int) x, (int) y, (int) z, sound.volume(), sound.pitch(), 0); + return new SoundEffectPacket(minestomSound, null, sound.source(), + new Vec(x, y, z), sound.volume(), sound.pitch(), 0); } } @@ -135,11 +136,9 @@ public class AdventurePacketConvertor { final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString()); if (minestomSound != null) { - return new EntitySoundEffectPacket(minestomSound.id(), sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), 0); + return new EntitySoundEffectPacket(minestomSound, null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), 0); } else { - final Pos pos = entity.getPosition(); - return new NamedSoundEffectPacket(sound.name().asString(), sound.source(), - (int) pos.x(), (int) pos.y(), (int) pos.z(), sound.volume(), sound.pitch(), 0); + return new EntitySoundEffectPacket(sound.name().asString(), null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), 0); } } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java index c2e67ae0f..baf582df2 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java @@ -14,7 +14,8 @@ public class ArgumentPotionEffect extends ArgumentRegistry { @Override public String parser() { - return "minecraft:mob_effect"; + // TODO: what replace `minecraft:mob_effect`? + return "minecraft:uuid"; } @Override diff --git a/src/main/java/net/minestom/server/crypto/ChatBound.java b/src/main/java/net/minestom/server/crypto/ChatBound.java new file mode 100644 index 000000000..5f2c7dc66 --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/ChatBound.java @@ -0,0 +1,22 @@ +package net.minestom.server.crypto; + +import net.kyori.adventure.text.Component; +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static net.minestom.server.network.NetworkBuffer.COMPONENT; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; + +public record ChatBound(int chatType, Component name, @Nullable Component targetName) implements NetworkBuffer.Writer { + public ChatBound(@NotNull NetworkBuffer reader) { + this(reader.read(VAR_INT), reader.read(COMPONENT), reader.readOptional(COMPONENT)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(VAR_INT, chatType); + writer.write(COMPONENT, name); + writer.writeOptional(COMPONENT, targetName); + } +} diff --git a/src/main/java/net/minestom/server/crypto/ChatSession.java b/src/main/java/net/minestom/server/crypto/ChatSession.java new file mode 100644 index 000000000..1cdcd253b --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/ChatSession.java @@ -0,0 +1,18 @@ +package net.minestom.server.crypto; + +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public record ChatSession(@NotNull UUID sessionId, @NotNull PlayerPublicKey publicKey) implements NetworkBuffer.Writer { + public ChatSession(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.UUID), new PlayerPublicKey(reader)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.UUID, sessionId); + writer.write(publicKey); + } +} diff --git a/src/main/java/net/minestom/server/crypto/FilterMask.java b/src/main/java/net/minestom/server/crypto/FilterMask.java new file mode 100644 index 000000000..2ffe575d4 --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/FilterMask.java @@ -0,0 +1,28 @@ +package net.minestom.server.crypto; + +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.util.BitSet; + +import static net.minestom.server.network.NetworkBuffer.LONG_ARRAY; + +public record FilterMask(@NotNull Type type, @NotNull BitSet mask) implements NetworkBuffer.Writer { + public FilterMask(@NotNull NetworkBuffer reader) { + this(reader.readEnum(Type.class), BitSet.valueOf(reader.read(LONG_ARRAY))); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeEnum(Type.class, type); + if (type == Type.PARTIALLY_FILTERED) { + writer.write(LONG_ARRAY, mask.toLongArray()); + } + } + + public enum Type { + PASS_THROUGH, + FULLY_FILTERED, + PARTIALLY_FILTERED + } +} diff --git a/src/main/java/net/minestom/server/crypto/LastSeenMessages.java b/src/main/java/net/minestom/server/crypto/LastSeenMessages.java index e6508888d..11c123813 100644 --- a/src/main/java/net/minestom/server/crypto/LastSeenMessages.java +++ b/src/main/java/net/minestom/server/crypto/LastSeenMessages.java @@ -2,45 +2,47 @@ package net.minestom.server.crypto; import net.minestom.server.network.NetworkBuffer; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import java.util.BitSet; import java.util.List; -import java.util.UUID; -public record LastSeenMessages(@NotNull List<@NotNull Entry> entries) implements NetworkBuffer.Writer { +import static net.minestom.server.network.NetworkBuffer.VAR_INT; + +public record LastSeenMessages(@NotNull List<@NotNull MessageSignature> entries) implements NetworkBuffer.Writer { public LastSeenMessages { entries = List.copyOf(entries); } public LastSeenMessages(@NotNull NetworkBuffer reader) { - this(reader.readCollection(Entry::new)); + this(reader.readCollection(MessageSignature::new)); } @Override public void write(@NotNull NetworkBuffer writer) { } - public record Entry(UUID from, MessageSignature lastSignature) implements NetworkBuffer.Writer { - public Entry(@NotNull NetworkBuffer reader) { - this(reader.read(NetworkBuffer.UUID), new MessageSignature(reader)); + public record Packed(@NotNull List entries) implements NetworkBuffer.Writer { + public static final Packed EMPTY = new Packed(List.of()); + + public Packed(@NotNull NetworkBuffer reader) { + this(reader.readCollection(MessageSignature.Packed::new)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(NetworkBuffer.UUID, from); - writer.write(lastSignature); + writer.writeCollection(entries); } } - public record Update(LastSeenMessages lastSeen, @Nullable Entry lastReceived) implements NetworkBuffer.Writer { + public record Update(int offset, @NotNull BitSet acknowledged) implements NetworkBuffer.Writer { public Update(@NotNull NetworkBuffer reader) { - this(new LastSeenMessages(reader), reader.readOptional(Entry::new)); + this(reader.read(VAR_INT), reader.readFixedBitSet(20)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(lastSeen); - writer.writeOptional(lastReceived); + writer.write(VAR_INT, offset); + writer.writeFixedBitSet(acknowledged, 20); } } } diff --git a/src/main/java/net/minestom/server/crypto/MessageSignature.java b/src/main/java/net/minestom/server/crypto/MessageSignature.java index d76e3b8e6..73885900c 100644 --- a/src/main/java/net/minestom/server/crypto/MessageSignature.java +++ b/src/main/java/net/minestom/server/crypto/MessageSignature.java @@ -2,16 +2,45 @@ package net.minestom.server.crypto; import net.minestom.server.network.NetworkBuffer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; -import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; +import static net.minestom.server.network.NetworkBuffer.RAW_BYTES; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; public record MessageSignature(byte @NotNull [] signature) implements NetworkBuffer.Writer { + public MessageSignature { + if (signature.length != 256) { + throw new IllegalArgumentException("Signature must be 256 bytes long"); + } + } + public MessageSignature(@NotNull NetworkBuffer reader) { - this(reader.read(BYTE_ARRAY)); + this(reader.readBytes(256)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(BYTE_ARRAY, signature); + writer.write(RAW_BYTES, signature); + } + + public record Packed(int id, @UnknownNullability MessageSignature fullSignature) implements NetworkBuffer.Writer { + public Packed(@NotNull NetworkBuffer reader) { + this(read(reader)); + } + + private Packed(@NotNull Packed packed) { + this(packed.id, packed.fullSignature); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(VAR_INT, id + 1); + if (id == 0) writer.write(fullSignature); + } + + private static Packed read(NetworkBuffer reader) { + final int id = reader.read(VAR_INT) - 1; + return new Packed(id, id == -1 ? new MessageSignature(reader) : null); + } } } diff --git a/src/main/java/net/minestom/server/crypto/SignedMessageBody.java b/src/main/java/net/minestom/server/crypto/SignedMessageBody.java new file mode 100644 index 000000000..fc1372280 --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/SignedMessageBody.java @@ -0,0 +1,31 @@ +package net.minestom.server.crypto; + +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; + +public final class SignedMessageBody { + + public record Packed(@NotNull String content, @NotNull Instant timeStamp, long salt, + LastSeenMessages.@NotNull Packed lastSeen) implements NetworkBuffer.Writer { + public Packed { + if (content.length() > 256) { + throw new IllegalArgumentException("Message content too long"); + } + } + + public Packed(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.STRING), Instant.ofEpochMilli(reader.read(NetworkBuffer.LONG)), + reader.read(NetworkBuffer.LONG), new LastSeenMessages.Packed(reader)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.STRING, content); + writer.write(NetworkBuffer.LONG, timeStamp.toEpochMilli()); + writer.write(NetworkBuffer.LONG, salt); + writer.write(lastSeen); + } + } +} diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 3559f514f..2ad606804 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -374,8 +374,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { // TODO: separate living entity categories soundCategory = Source.HOSTILE; } - sendPacketToViewersAndSelf(new SoundEffectPacket(sound, soundCategory, - getPosition(), 1.0f, 1.0f)); + sendPacketToViewersAndSelf(new SoundEffectPacket(sound, null, soundCategory, + getPosition(), 1.0f, 1.0f, 0)); } }); diff --git a/src/main/java/net/minestom/server/entity/Metadata.java b/src/main/java/net/minestom/server/entity/Metadata.java index b70d0d253..6cfffe1af 100644 --- a/src/main/java/net/minestom/server/entity/Metadata.java +++ b/src/main/java/net/minestom/server/entity/Metadata.java @@ -28,6 +28,10 @@ public final class Metadata { return new MetadataImpl.EntryImpl<>(TYPE_VARINT, value, NetworkBuffer.VAR_INT); } + public static Entry Long(long value) { + return new MetadataImpl.EntryImpl<>(TYPE_LONG, value, NetworkBuffer.LONG); + } + public static Entry Float(float value) { return new MetadataImpl.EntryImpl<>(TYPE_FLOAT, value, NetworkBuffer.FLOAT); } @@ -97,23 +101,24 @@ public final class Metadata { public static final byte TYPE_BYTE = 0; public static final byte TYPE_VARINT = 1; - public static final byte TYPE_FLOAT = 2; - public static final byte TYPE_STRING = 3; - public static final byte TYPE_CHAT = 4; - public static final byte TYPE_OPTCHAT = 5; - public static final byte TYPE_SLOT = 6; - public static final byte TYPE_BOOLEAN = 7; - public static final byte TYPE_ROTATION = 8; - public static final byte TYPE_POSITION = 9; - public static final byte TYPE_OPTPOSITION = 10; - public static final byte TYPE_DIRECTION = 11; - public static final byte TYPE_OPTUUID = 12; - public static final byte TYPE_OPTBLOCKID = 13; - public static final byte TYPE_NBT = 14; - public static final byte TYPE_PARTICLE = 15; - public static final byte TYPE_VILLAGERDATA = 16; - public static final byte TYPE_OPTVARINT = 17; - public static final byte TYPE_POSE = 18; + public static final byte TYPE_LONG = 2; + public static final byte TYPE_FLOAT = 3; + public static final byte TYPE_STRING = 4; + public static final byte TYPE_CHAT = 5; + public static final byte TYPE_OPTCHAT = 6; + public static final byte TYPE_SLOT = 7; + public static final byte TYPE_BOOLEAN = 8; + public static final byte TYPE_ROTATION = 9; + public static final byte TYPE_POSITION = 10; + public static final byte TYPE_OPTPOSITION = 11; + public static final byte TYPE_DIRECTION = 12; + public static final byte TYPE_OPTUUID = 13; + public static final byte TYPE_OPTBLOCKID = 14; + public static final byte TYPE_NBT = 15; + public static final byte TYPE_PARTICLE = 16; + public static final byte TYPE_VILLAGERDATA = 17; + public static final byte TYPE_OPTVARINT = 18; + public static final byte TYPE_POSE = 19; private static final VarHandle NOTIFIED_CHANGES; diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index b3972f5ad..4f1bdc4eb 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -452,7 +452,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, refreshHealth(); sendPacket(new RespawnPacket(getDimensionType().toString(), getDimensionType().getName().asString(), - 0, gameMode, gameMode, false, levelFlat, true, deathLocation)); + 0, gameMode, gameMode, false, levelFlat, true, deathLocation)); PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this); EventDispatcher.call(respawnEvent); @@ -944,8 +944,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, */ public void setDisplayName(@Nullable Component displayName) { this.displayName = displayName; - PacketUtils.broadcastPacket(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - new PlayerInfoPacket.UpdateDisplayName(getUuid(), displayName))); + PacketUtils.broadcastPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, infoEntry())); } /** @@ -973,8 +972,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(getEntityId()); - final PlayerInfoPacket removePlayerPacket = getRemovePlayerToList(); - final PlayerInfoPacket addPlayerPacket = getAddPlayerToList(); + final PlayerInfoRemovePacket removePlayerPacket = getRemovePlayerToList(); + final PlayerInfoUpdatePacket addPlayerPacket = getAddPlayerToList(); RespawnPacket respawnPacket = new RespawnPacket(getDimensionType().toString(), getDimensionType().getName().asString(), 0, gameMode, gameMode, false, levelFlat, true, deathLocation); @@ -1308,8 +1307,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // Condition to prevent sending the packets before spawning the player if (isActive()) { sendPacket(new ChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.id())); - PacketUtils.broadcastPacket(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE, - new PlayerInfoPacket.UpdateGameMode(getUuid(), gameMode))); + PacketUtils.broadcastPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, infoEntry())); } // The client updates their abilities based on the GameMode as follows @@ -1809,8 +1807,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, */ public void refreshLatency(int latency) { this.latency = latency; - PacketUtils.broadcastPacket(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY, - new PlayerInfoPacket.UpdateLatency(getUuid(), latency))); + PacketUtils.broadcastPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, infoEntry())); } public void refreshOnGround(boolean onGround) { @@ -1928,24 +1925,29 @@ public class Player extends LivingEntity implements CommandSender, Localizable, /** * Gets the packet to add the player from the tab-list. * - * @return a {@link PlayerInfoPacket} to add the player + * @return a {@link PlayerInfoUpdatePacket} to add the player */ - protected @NotNull PlayerInfoPacket getAddPlayerToList() { - final PlayerSkin skin = this.skin; - List prop = skin != null ? - List.of(new PlayerInfoPacket.AddPlayer.Property("textures", skin.textures(), skin.signature())) : - List.of(); - return new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER, - new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), prop, getGameMode(), getLatency(), displayName, null)); + protected @NotNull PlayerInfoUpdatePacket getAddPlayerToList() { + return new PlayerInfoUpdatePacket(EnumSet.of(PlayerInfoUpdatePacket.Action.ADD_PLAYER, PlayerInfoUpdatePacket.Action.UPDATE_LISTED), + List.of(infoEntry())); } /** * Gets the packet to remove the player from the tab-list. * - * @return a {@link PlayerInfoPacket} to remove the player + * @return a {@link PlayerInfoRemovePacket} to remove the player */ - protected @NotNull PlayerInfoPacket getRemovePlayerToList() { - return new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER, new PlayerInfoPacket.RemovePlayer(getUuid())); + protected @NotNull PlayerInfoRemovePacket getRemovePlayerToList() { + return new PlayerInfoRemovePacket(getUuid()); + } + + private PlayerInfoUpdatePacket.Entry infoEntry() { + final PlayerSkin skin = this.skin; + List prop = skin != null ? + List.of(new PlayerInfoUpdatePacket.Property("textures", skin.textures(), skin.signature())) : + List.of(); + return new PlayerInfoUpdatePacket.Entry(getUuid(), getUsername(), prop, + true, getLatency(), getGameMode(), displayName, null); } /** diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index 11110d8f2..6ff7431e5 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -16,9 +16,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.Collection; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -204,6 +202,43 @@ public final class NetworkBuffer { return enumClass.getEnumConstants()[read(VAR_INT)]; } + public > void writeEnumSet(EnumSet enumSet, Class enumType) { + final E[] values = enumType.getEnumConstants(); + BitSet bitSet = new BitSet(values.length); + for (int i = 0; i < values.length; ++i) { + bitSet.set(i, enumSet.contains(values[i])); + } + writeFixedBitSet(bitSet, values.length); + } + + public > @NotNull EnumSet readEnumSet(Class enumType) { + final E[] values = enumType.getEnumConstants(); + BitSet bitSet = readFixedBitSet(values.length); + EnumSet enumSet = EnumSet.noneOf(enumType); + for (int i = 0; i < values.length; ++i) { + if (bitSet.get(i)) { + enumSet.add(values[i]); + } + } + return enumSet; + } + + public void writeFixedBitSet(BitSet set, int length) { + final int setLength = set.length(); + if (setLength > length) { + throw new IllegalArgumentException("BitSet is larger than expected size (" + setLength + ">" + length + ")"); + } else { + final byte[] array = set.toByteArray(); + write(RAW_BYTES, array); + } + } + + @NotNull + public BitSet readFixedBitSet(int length) { + final byte[] array = readBytes((length + 7) / 8); + return BitSet.valueOf(array); + } + public byte[] readBytes(int length) { byte[] bytes = new byte[length]; nioBuffer.get(readIndex, bytes, 0, length); diff --git a/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java b/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java index 25fd1a4c1..e381d1a63 100644 --- a/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java +++ b/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java @@ -58,33 +58,33 @@ public sealed class ClientPacketsHandler permits ClientPacketsHandler.Status, Cl register(0x03, ClientChatAckPacket::new); register(0x04, ClientCommandChatPacket::new); register(0x05, ClientChatMessagePacket::new); - register(0x06, ClientChatPreviewPacket::new); - register(0x07, ClientStatusPacket::new); - register(0x08, ClientSettingsPacket::new); - register(0x09, ClientTabCompletePacket::new); - register(0x0A, ClientClickWindowButtonPacket::new); - register(0x0B, ClientClickWindowPacket::new); - register(0x0C, ClientCloseWindowPacket::new); - register(0x0D, ClientPluginMessagePacket::new); - register(0x0E, ClientEditBookPacket::new); - register(0x0F, ClientQueryEntityNbtPacket::new); - register(0x10, ClientInteractEntityPacket::new); - register(0x11, ClientGenerateStructurePacket::new); - register(0x12, ClientKeepAlivePacket::new); + register(0x06, ClientStatusPacket::new); + register(0x07, ClientSettingsPacket::new); + register(0x08, ClientTabCompletePacket::new); + register(0x09, ClientClickWindowButtonPacket::new); + register(0x0A, ClientClickWindowPacket::new); + register(0x0B, ClientCloseWindowPacket::new); + register(0x0C, ClientPluginMessagePacket::new); + register(0x0D, ClientEditBookPacket::new); + register(0x0E, ClientQueryEntityNbtPacket::new); + register(0x0F, ClientInteractEntityPacket::new); + register(0x10, ClientGenerateStructurePacket::new); + register(0x11, ClientKeepAlivePacket::new); // 0x12 packet not used server-side - register(0x14, ClientPlayerPositionPacket::new); - register(0x15, ClientPlayerPositionAndRotationPacket::new); - register(0x16, ClientPlayerRotationPacket::new); - register(0x17, ClientPlayerPacket::new); - register(0x18, ClientVehicleMovePacket::new); - register(0x19, ClientSteerBoatPacket::new); - register(0x1A, ClientPickItemPacket::new); - register(0x1B, ClientCraftRecipeRequest::new); - register(0x1C, ClientPlayerAbilitiesPacket::new); - register(0x1D, ClientPlayerDiggingPacket::new); - register(0x1E, ClientEntityActionPacket::new); - register(0x1F, ClientSteerVehiclePacket::new); - register(0x20, ClientPongPacket::new); + register(0x13, ClientPlayerPositionPacket::new); + register(0x14, ClientPlayerPositionAndRotationPacket::new); + register(0x15, ClientPlayerRotationPacket::new); + register(0x16, ClientPlayerPacket::new); + register(0x17, ClientVehicleMovePacket::new); + register(0x18, ClientSteerBoatPacket::new); + register(0x19, ClientPickItemPacket::new); + register(0x1A, ClientCraftRecipeRequest::new); + register(0x1B, ClientPlayerAbilitiesPacket::new); + register(0x1C, ClientPlayerDiggingPacket::new); + register(0x1D, ClientEntityActionPacket::new); + register(0x1E, ClientSteerVehiclePacket::new); + register(0x1F, ClientPongPacket::new); + register(0x20, ClientChatSessionUpdatePacket::new); register(0x21, ClientSetRecipeBookStatePacket::new); register(0x22, ClientSetDisplayedRecipePacket::new); register(0x23, ClientNameItemPacket::new); diff --git a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java index f622a16b7..eaf57a25c 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java @@ -3,18 +3,13 @@ package net.minestom.server.network.packet.client.login; import com.google.gson.Gson; import com.google.gson.JsonObject; import net.minestom.server.MinecraftServer; -import net.minestom.server.crypto.SaltSignaturePair; -import net.minestom.server.crypto.SignatureValidator; import net.minestom.server.extras.MojangAuth; import net.minestom.server.extras.mojangAuth.MojangCrypt; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPreplayPacket; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; -import net.minestom.server.utils.Either; -import net.minestom.server.utils.InterfaceUtils; import net.minestom.server.utils.async.AsyncUtils; -import net.minestom.server.utils.crypto.KeyUtils; import org.jetbrains.annotations.NotNull; import javax.crypto.SecretKey; @@ -28,14 +23,14 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.UUID; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; public record EncryptionResponsePacket(byte[] sharedSecret, - Either nonceOrSignature) implements ClientPreplayPacket { + byte[] encryptedVerifyToken) implements ClientPreplayPacket { private static final Gson GSON = new Gson(); public EncryptionResponsePacket(@NotNull NetworkBuffer reader) { - this(reader.read(BYTE_ARRAY), reader.readEither(networkBuffer -> networkBuffer.read(BYTE_ARRAY), SaltSignaturePair::new)); + this(reader.read(BYTE_ARRAY), reader.read(BYTE_ARRAY)); } @Override @@ -50,15 +45,8 @@ public record EncryptionResponsePacket(byte[] sharedSecret, } final boolean hasPublicKey = connection.playerPublicKey() != null; - final boolean verificationFailed = nonceOrSignature.map( - nonce -> hasPublicKey || !Arrays.equals(socketConnection.getNonce(), - MojangCrypt.decryptUsingKey(MojangAuth.getKeyPair().getPrivate(), nonce)), - signature -> !hasPublicKey || !SignatureValidator - .from(connection.playerPublicKey().publicKey(), KeyUtils.SignatureAlgorithm.SHA256withRSA) - .validate(binaryWriter -> { - binaryWriter.write(RAW_BYTES, socketConnection.getNonce()); - binaryWriter.write(LONG, signature.salt()); - }, signature.signature())); + final boolean verificationFailed = hasPublicKey || !Arrays.equals(socketConnection.getNonce(), + MojangCrypt.decryptUsingKey(MojangAuth.getKeyPair().getPrivate(), encryptedVerifyToken)); if (verificationFailed) { MinecraftServer.LOGGER.error("Encryption failed for {}", loginUsername); @@ -111,8 +99,7 @@ public record EncryptionResponsePacket(byte[] sharedSecret, @Override public void write(@NotNull NetworkBuffer writer) { writer.write(BYTE_ARRAY, sharedSecret); - writer.writeEither(nonceOrSignature, (networkBuffer, bytes) -> networkBuffer.write(BYTE_ARRAY, bytes), - InterfaceUtils.flipBiConsumer(SaltSignaturePair::write)); + writer.write(BYTE_ARRAY, encryptedVerifyToken); } private SecretKey getSecretKey() { diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java index 07716f76c..239b68367 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java @@ -2,9 +2,6 @@ package net.minestom.server.network.packet.client.login; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.minestom.server.MinecraftServer; -import net.minestom.server.crypto.PlayerPublicKey; -import net.minestom.server.crypto.SignatureValidator; import net.minestom.server.extras.MojangAuth; import net.minestom.server.extras.bungee.BungeeCordProxy; import net.minestom.server.extras.velocity.VelocityProxy; @@ -19,45 +16,22 @@ import net.minestom.server.network.player.PlayerSocketConnection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.time.Instant; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.STRING; +import static net.minestom.server.network.NetworkBuffer.UUID; public record LoginStartPacket(@NotNull String username, - @Nullable PlayerPublicKey publicKey, @Nullable UUID profileId) implements ClientPreplayPacket { private static final Component ALREADY_CONNECTED = Component.text("You are already on this server", NamedTextColor.RED); public LoginStartPacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), reader.readOptional(PlayerPublicKey::new), reader.readOptional(UUID)); + this(reader.read(STRING), reader.readOptional(UUID)); } @Override public void process(@NotNull PlayerConnection connection) { - // TODO use uuid - // TODO configurable check & messages - if (publicKey != null) { - if (!SignatureValidator.YGGDRASIL.validate(binaryWriter -> { - if (profileId != null) { - binaryWriter.write(LONG, profileId.getMostSignificantBits()); - binaryWriter.write(LONG, profileId.getLeastSignificantBits()); - } else { - MinecraftServer.LOGGER.warn("Profile ID was null for player {}, signature will not match!", username); - } - binaryWriter.write(LONG, publicKey.expiresAt().toEpochMilli()); - binaryWriter.write(RAW_BYTES, publicKey.publicKey().getEncoded()); - }, publicKey.signature())) { - connection.sendPacket(new LoginDisconnectPacket(Component.text("Invalid Profile Public Key!"))); - connection.disconnect(); - } - if (publicKey.expiresAt().isBefore(Instant.now())) { - connection.sendPacket(new LoginDisconnectPacket(Component.text("Expired Profile Public Key!"))); - connection.disconnect(); - } - connection.setPlayerPublicKey(publicKey); - } final boolean isSocketConnection = connection instanceof PlayerSocketConnection; // Proxy support (only for socket clients) and cache the login username if (isSocketConnection) { diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java index b5cc19c9e..9f448d037 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java @@ -1,17 +1,18 @@ package net.minestom.server.network.packet.client.play; -import net.minestom.server.crypto.LastSeenMessages; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; import org.jetbrains.annotations.NotNull; -public record ClientChatAckPacket(@NotNull LastSeenMessages.Update update) implements ClientPacket { +import static net.minestom.server.network.NetworkBuffer.VAR_INT; + +public record ClientChatAckPacket(int offset) implements ClientPacket { public ClientChatAckPacket(@NotNull NetworkBuffer reader) { - this(new LastSeenMessages.Update(reader)); + this(reader.read(VAR_INT)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(update); + writer.write(VAR_INT, offset); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java index 8f79ee147..3da9f83f4 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java @@ -1,28 +1,23 @@ package net.minestom.server.network.packet.client.play; -import net.minestom.server.crypto.LastSeenMessages; -import net.minestom.server.crypto.MessageSignature; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.BitSet; import static net.minestom.server.network.NetworkBuffer.*; -public record ClientChatMessagePacket(@NotNull String message, - long timestamp, long salt, @NotNull MessageSignature signature, - boolean signedPreview, - @NotNull LastSeenMessages.Update lastSeenMessages) implements ClientPacket { - public ClientChatMessagePacket { - if (message.length() > 256) { - throw new IllegalArgumentException("Message cannot be more than 256 characters long."); - } - } +public record ClientChatMessagePacket(String message, long timestamp, + long salt, byte @Nullable [] signature, + int ackOffset, BitSet ackList) implements ClientPacket { public ClientChatMessagePacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), - reader.read(LONG), reader.read(LONG), new MessageSignature(reader), - reader.read(BOOLEAN), - new LastSeenMessages.Update(reader)); + this(reader.read(STRING), reader.read(LONG), + reader.read(LONG), reader.readOptional(r -> r.readBytes(256)), + reader.read(VAR_INT), BitSet.valueOf(reader.readBytes(3))); } @Override @@ -30,8 +25,8 @@ public record ClientChatMessagePacket(@NotNull String message, writer.write(STRING, message); writer.write(LONG, timestamp); writer.write(LONG, salt); - writer.write(signature); - writer.write(BOOLEAN, signedPreview); - writer.write(lastSeenMessages); + writer.writeOptional(BYTE_ARRAY, signature); + writer.write(VAR_INT, ackOffset); + writer.write(RAW_BYTES, Arrays.copyOf(ackList.toByteArray(), 3)); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatPreviewPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatPreviewPacket.java deleted file mode 100644 index 9586976dc..000000000 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatPreviewPacket.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.minestom.server.network.packet.client.play; - -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.client.ClientPacket; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.INT; -import static net.minestom.server.network.NetworkBuffer.STRING; - -public record ClientChatPreviewPacket(int queryId, @NotNull String query) implements ClientPacket { - public ClientChatPreviewPacket { - if (query.length() > 256) { - throw new IllegalArgumentException("Query length cannot be greater than 256"); - } - } - - public ClientChatPreviewPacket(@NotNull NetworkBuffer reader) { - this(reader.read(INT), reader.read(STRING)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(INT, queryId); - writer.write(STRING, query); - } -} diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatSessionUpdatePacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatSessionUpdatePacket.java new file mode 100644 index 000000000..a18b33e9a --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatSessionUpdatePacket.java @@ -0,0 +1,17 @@ +package net.minestom.server.network.packet.client.play; + +import net.minestom.server.crypto.ChatSession; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.client.ClientPacket; +import org.jetbrains.annotations.NotNull; + +public record ClientChatSessionUpdatePacket(@NotNull ChatSession chatSession) implements ClientPacket { + public ClientChatSessionUpdatePacket(@NotNull NetworkBuffer reader) { + this(new ChatSession(reader)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(chatSession); + } +} diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java index a9d481ae5..ef29ff546 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java @@ -6,11 +6,11 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; import org.jetbrains.annotations.NotNull; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.LONG; +import static net.minestom.server.network.NetworkBuffer.STRING; public record ClientCommandChatPacket(@NotNull String message, long timestamp, long salt, @NotNull ArgumentSignatures signatures, - boolean signedPreview, LastSeenMessages.@NotNull Update lastSeenMessages) implements ClientPacket { public ClientCommandChatPacket { if (message.length() > 256) { @@ -20,7 +20,8 @@ public record ClientCommandChatPacket(@NotNull String message, long timestamp, public ClientCommandChatPacket(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(LONG), - reader.read(LONG), new ArgumentSignatures(reader), reader.read(BOOLEAN), new LastSeenMessages.Update(reader)); + reader.read(LONG), new ArgumentSignatures(reader), + new LastSeenMessages.Update(reader)); } @Override @@ -29,7 +30,6 @@ public record ClientCommandChatPacket(@NotNull String message, long timestamp, writer.write(LONG, timestamp); writer.write(LONG, salt); writer.write(signatures); - writer.write(BOOLEAN, signedPreview); writer.write(lastSeenMessages); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java index fc4a119e5..8dc276767 100644 --- a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java +++ b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java @@ -23,7 +23,6 @@ public final class ServerPacketIdentifier { public static final int BLOCK_CHANGE = nextPlayId(); public static final int BOSS_BAR = nextPlayId(); public static final int SERVER_DIFFICULTY = nextPlayId(); - public static final int CHAT_PREVIEW = nextPlayId(); public static final int CLEAR_TITLES = nextPlayId(); public static final int TAB_COMPLETE = nextPlayId(); public static final int DECLARE_COMMANDS = nextPlayId(); @@ -34,9 +33,9 @@ public final class ServerPacketIdentifier { public static final int SET_COOLDOWN = nextPlayId(); public static final int CUSTOM_CHAT_COMPLETIONS = nextPlayId(); public static final int PLUGIN_MESSAGE = nextPlayId(); - public static final int NAMED_SOUND_EFFECT = nextPlayId(); public static final int DELETE_CHAT_MESSAGE = nextPlayId(); public static final int DISCONNECT = nextPlayId(); + public static final int DISGUISED_CHAT = nextPlayId(); public static final int ENTITY_STATUS = nextPlayId(); public static final int EXPLOSION = nextPlayId(); public static final int UNLOAD_CHUNK = nextPlayId(); @@ -61,12 +60,12 @@ public final class ServerPacketIdentifier { public static final int PING = nextPlayId(); public static final int CRAFT_RECIPE_RESPONSE = nextPlayId(); public static final int PLAYER_ABILITIES = nextPlayId(); - public static final int PLAYER_CHAT_HEADER = nextPlayId(); public static final int PLAYER_CHAT = nextPlayId(); public static final int END_COMBAT_EVENT = nextPlayId(); public static final int ENTER_COMBAT_EVENT = nextPlayId(); public static final int DEATH_COMBAT_EVENT = nextPlayId(); - public static final int PLAYER_INFO = nextPlayId(); + public static final int PLAYER_INFO_REMOVE = nextPlayId(); + public static final int PLAYER_INFO_UPDATE = nextPlayId(); public static final int FACE_PLAYER = nextPlayId(); public static final int PLAYER_POSITION_AND_LOOK = nextPlayId(); public static final int UNLOCK_RECIPES = nextPlayId(); @@ -89,7 +88,6 @@ public final class ServerPacketIdentifier { public static final int UPDATE_VIEW_POSITION = nextPlayId(); public static final int UPDATE_VIEW_DISTANCE = nextPlayId(); // Not used by the dedicated server public static final int SPAWN_POSITION = nextPlayId(); - public static final int SET_DISPLAY_CHAT_PREVIEW = nextPlayId(); public static final int DISPLAY_SCOREBOARD = nextPlayId(); public static final int ENTITY_METADATA = nextPlayId(); public static final int ATTACH_ENTITY = nextPlayId(); @@ -116,6 +114,7 @@ public final class ServerPacketIdentifier { public static final int ENTITY_TELEPORT = nextPlayId(); public static final int ADVANCEMENTS = nextPlayId(); public static final int ENTITY_PROPERTIES = nextPlayId(); + public static final int UPDATE_ENABLED_FEATURES = nextPlayId(); public static final int ENTITY_EFFECT = nextPlayId(); public static final int DECLARE_RECIPES = nextPlayId(); public static final int TAGS = nextPlayId(); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChatPreviewPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChatPreviewPacket.java deleted file mode 100644 index a197df2ac..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChatPreviewPacket.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.kyori.adventure.text.Component; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.List; -import java.util.function.UnaryOperator; - -import static net.minestom.server.network.NetworkBuffer.*; - -public record ChatPreviewPacket(int queryId, @Nullable Component preview) implements ComponentHoldingServerPacket { - public ChatPreviewPacket(@NotNull NetworkBuffer reader) { - this(reader.read(INT), reader.readOptional(COMPONENT)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(INT, queryId); - writer.writeOptional(COMPONENT, preview); - } - - @Override - public int getId() { - return ServerPacketIdentifier.CHAT_PREVIEW; - } - - @Override - public @NotNull Collection components() { - return List.of(preview); - } - - @Override - public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { - return new ChatPreviewPacket(queryId, operator.apply(preview)); - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java index 6d93af459..1da9e5752 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java @@ -2,23 +2,86 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.sound.Sound; import net.minestom.server.adventure.AdventurePacketConvertor; +import net.minestom.server.coordinate.Point; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.sound.SoundEvent; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; -public record EntitySoundEffectPacket(int soundId, Sound.Source source, int entityId, - float volume, float pitch, long seed) implements ServerPacket { +public record EntitySoundEffectPacket( + // only one of soundEvent and soundName may be present + @Nullable SoundEvent soundEvent, + @Nullable String soundName, + @Nullable Float range, // Only allowed with soundName + @NotNull Sound.Source source, + int entityId, + float volume, + float pitch, + long seed +) implements ServerPacket { + + public EntitySoundEffectPacket { + Check.argCondition(soundEvent == null && soundName == null, "soundEvent and soundName cannot both be null"); + Check.argCondition(soundEvent != null && soundName != null, "soundEvent and soundName cannot both be present"); + } + + public EntitySoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Sound.Source source, + int entityId, float volume, float pitch, long seed) { + this(soundEvent, null, range, source, entityId, volume, pitch, seed); + } + + public EntitySoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Sound.Source source, + int entityId, float volume, float pitch, long seed) { + this(null, soundName, range, source, entityId, volume, pitch, seed); + } + public EntitySoundEffectPacket(@NotNull NetworkBuffer reader) { - this(reader.read(VAR_INT), Sound.Source.values()[reader.read(VAR_INT)], reader.read(VAR_INT), - reader.read(FLOAT), reader.read(FLOAT), reader.read(LONG)); + this(fromReader(reader)); + } + + private EntitySoundEffectPacket(@NotNull EntitySoundEffectPacket packet) { + this(packet.soundEvent, packet.soundName, packet.range, packet.source, packet.entityId, packet.volume, packet.pitch, packet.seed); + } + + private static @NotNull EntitySoundEffectPacket fromReader(@NotNull NetworkBuffer reader) { + int soundId = reader.read(VAR_INT); + SoundEvent soundEvent; + String soundName; + Float range = null; + if (soundId == 0) { + soundEvent = null; + soundName = reader.read(STRING); + range = reader.readOptional(FLOAT); + } else { + soundEvent = SoundEvent.fromId(soundId - 1); + soundName = null; + } + return new EntitySoundEffectPacket( + soundEvent, + soundName, + range, + reader.readEnum(Sound.Source.class), + reader.read(VAR_INT), + reader.read(FLOAT), + reader.read(FLOAT), + reader.read(LONG) + ); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, soundId); + if (soundEvent != null) { + writer.write(VAR_INT, soundEvent.id() + 1); + } else { + writer.write(VAR_INT, 0); + writer.write(STRING, soundName); + writer.writeOptional(FLOAT, null); + } writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(VAR_INT, entityId); writer.write(FLOAT, volume); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java index 3ff4ddaea..145d7ced9 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java @@ -7,19 +7,19 @@ import org.jetbrains.annotations.NotNull; import static net.minestom.server.network.NetworkBuffer.*; -public record ExplosionPacket(float x, float y, float z, float radius, byte @NotNull [] records, +public record ExplosionPacket(double x, double y, double z, float radius, byte @NotNull [] records, float playerMotionX, float playerMotionY, float playerMotionZ) implements ServerPacket { public ExplosionPacket(@NotNull NetworkBuffer reader) { - this(reader.read(FLOAT), reader.read(FLOAT), reader.read(FLOAT), + this(reader.read(DOUBLE), reader.read(DOUBLE), reader.read(DOUBLE), reader.read(FLOAT), reader.readBytes(reader.read(VAR_INT) * 3), reader.read(FLOAT), reader.read(FLOAT), reader.read(FLOAT)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(FLOAT, x); - writer.write(FLOAT, y); - writer.write(FLOAT, z); + writer.write(DOUBLE, x); + writer.write(DOUBLE, y); + writer.write(DOUBLE, z); writer.write(FLOAT, radius); writer.write(VAR_INT, records.length / 3); // each record is 3 bytes long writer.write(RAW_BYTES, records); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/NamedSoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/NamedSoundEffectPacket.java deleted file mode 100644 index 7ceaea8ec..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/NamedSoundEffectPacket.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.kyori.adventure.sound.Sound.Source; -import net.minestom.server.adventure.AdventurePacketConvertor; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.*; - -public record NamedSoundEffectPacket(String soundName, Source source, int x, int y, int z, - float volume, float pitch, long seed) implements ServerPacket { - public NamedSoundEffectPacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), Source.values()[reader.read(VAR_INT)], - reader.read(INT) / 8, reader.read(INT) / 8, reader.read(INT) / 8, - reader.read(FLOAT), reader.read(FLOAT), reader.read(LONG)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(STRING, soundName); - writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); - writer.write(INT, x * 8); - writer.write(INT, y * 8); - writer.write(INT, z * 8); - writer.write(FLOAT, volume); - writer.write(FLOAT, pitch); - writer.write(LONG, seed); - } - - @Override - public int getId() { - return ServerPacketIdentifier.NAMED_SOUND_EFFECT; - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatHeaderPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatHeaderPacket.java deleted file mode 100644 index 2a13bab72..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatHeaderPacket.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.minestom.server.crypto.MessageSignature; -import net.minestom.server.crypto.SignedMessageHeader; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; - -public record PlayerChatHeaderPacket(@NotNull SignedMessageHeader messageHeader, @NotNull MessageSignature signature, - byte[] bodyDigest) implements ServerPacket { - public PlayerChatHeaderPacket(@NotNull NetworkBuffer reader) { - this(new SignedMessageHeader(reader), new MessageSignature(reader), reader.read(BYTE_ARRAY)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(messageHeader); - writer.write(signature); - writer.write(BYTE_ARRAY, bodyDigest); - } - - @Override - public int getId() { - return ServerPacketIdentifier.PLAYER_CHAT_HEADER; - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java index 143867aef..309eac8df 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java @@ -1,7 +1,8 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; -import net.minestom.server.crypto.MessageSignature; +import net.minestom.server.crypto.FilterMask; +import net.minestom.server.crypto.SignedMessageBody; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; @@ -9,6 +10,7 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -19,26 +21,30 @@ import static net.minestom.server.network.NetworkBuffer.*; /** * Represents an outgoing chat message packet. */ -public record PlayerChatMessagePacket(@NotNull Component signedContent, @Nullable Component unsignedContent, - int type, @NotNull UUID uuid, - @NotNull Component displayName, @Nullable Component teamDisplayName, - @NotNull MessageSignature signature) implements ComponentHoldingServerPacket { +public record PlayerChatMessagePacket(UUID sender, int index, byte @Nullable [] signature, + SignedMessageBody.@NotNull Packed messageBody, + @Nullable Component unsignedContent, FilterMask filterMask, + int msgTypeId, Component msgTypeName, + @Nullable Component msgTypeTarget) implements ComponentHoldingServerPacket { public PlayerChatMessagePacket(@NotNull NetworkBuffer reader) { - this(reader.read(COMPONENT), reader.readOptional(COMPONENT), - reader.read(VAR_INT), reader.read(NetworkBuffer.UUID), - reader.read(COMPONENT), reader.readOptional(COMPONENT), - new MessageSignature(reader)); + this(reader.read(UUID), reader.read(VAR_INT), reader.readOptional(r -> r.readBytes(256)), + new SignedMessageBody.Packed(reader), + reader.readOptional(COMPONENT), new FilterMask(reader), + reader.read(VAR_INT), reader.read(COMPONENT), + reader.readOptional(COMPONENT)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(COMPONENT, signedContent); + writer.write(UUID, sender); + writer.write(VAR_INT, index); + writer.writeOptional(RAW_BYTES, signature); + writer.write(messageBody); writer.writeOptional(COMPONENT, unsignedContent); - writer.write(VAR_INT, type); - writer.write(UUID, uuid); - writer.write(COMPONENT, displayName); - writer.writeOptional(COMPONENT, teamDisplayName); - writer.write(signature); + writer.write(filterMask); + writer.write(VAR_INT, msgTypeId); + writer.write(COMPONENT, msgTypeName); + writer.writeOptional(COMPONENT, msgTypeTarget); } @Override @@ -48,12 +54,19 @@ public record PlayerChatMessagePacket(@NotNull Component signedContent, @Nullabl @Override public @NotNull Collection components() { - return List.of(signedContent); + final ArrayList list = new ArrayList<>(); + list.add(msgTypeName); + if (unsignedContent != null) list.add(unsignedContent); + if (msgTypeTarget != null) list.add(msgTypeTarget); + return List.copyOf(list); } @Override public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { - return new PlayerChatMessagePacket(signedContent, unsignedContent, type, - uuid, displayName, teamDisplayName, signature); + return new PlayerChatMessagePacket(sender, index, signature, + messageBody, + operator.apply(unsignedContent), filterMask, + msgTypeId, operator.apply(msgTypeName), + operator.apply(msgTypeTarget)); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java deleted file mode 100644 index 5e21cc4e8..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java +++ /dev/null @@ -1,242 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.kyori.adventure.text.Component; -import net.minestom.server.adventure.ComponentHolder; -import net.minestom.server.crypto.PlayerPublicKey; -import net.minestom.server.entity.GameMode; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.UUID; -import java.util.function.UnaryOperator; - -import static net.minestom.server.network.NetworkBuffer.*; - -public record PlayerInfoPacket(@NotNull Action action, - @NotNull List entries) implements ComponentHoldingServerPacket { - public PlayerInfoPacket { - entries = List.copyOf(entries); - for (Entry entry : entries) { - if (!entry.getClass().equals(action.getClazz())) - throw new IllegalArgumentException("Invalid entry class for action " + action); - } - } - - public PlayerInfoPacket(@NotNull Action action, @NotNull Entry entry) { - this(action, List.of(entry)); - } - - public PlayerInfoPacket(@NotNull NetworkBuffer reader) { - this(read(reader)); - } - - private PlayerInfoPacket(PlayerInfoPacket packet) { - this(packet.action, packet.entries); - } - - private static PlayerInfoPacket read(@NotNull NetworkBuffer reader) { - var action = Action.values()[reader.read(VAR_INT)]; - final int playerInfoCount = reader.read(VAR_INT); - List entries = new ArrayList<>(playerInfoCount); - for (int i = 0; i < playerInfoCount; i++) { - final UUID uuid = reader.read(UUID); - entries.add(switch (action) { - case ADD_PLAYER -> new AddPlayer(uuid, reader); - case UPDATE_GAMEMODE -> new UpdateGameMode(uuid, reader); - case UPDATE_LATENCY -> new UpdateLatency(uuid, reader); - case UPDATE_DISPLAY_NAME -> new UpdateDisplayName(uuid, reader); - case REMOVE_PLAYER -> new RemovePlayer(uuid); - }); - } - return new PlayerInfoPacket(action, entries); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, action.ordinal()); - writer.writeCollection(entries, (w, entry) -> { - w.write(UUID, entry.uuid()); - entry.write(w); - }); - } - - @Override - public int getId() { - return ServerPacketIdentifier.PLAYER_INFO; - } - - @Override - public @NotNull Collection components() { - switch (this.action) { - case ADD_PLAYER, UPDATE_DISPLAY_NAME -> { - List components = new ArrayList<>(); - for (Entry entry : entries) { - if (entry instanceof ComponentHolder) { - components.addAll(((ComponentHolder) entry).components()); - } - } - return components; - } - default -> { - return List.of(); - } - } - } - - @Override - public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { - return switch (action) { - case ADD_PLAYER, UPDATE_DISPLAY_NAME -> { - List entries = new ArrayList<>(this.entries.size()); - for (Entry entry : this.entries) { - if (entry instanceof ComponentHolder) { - entries.add(((ComponentHolder) entry).copyWithOperator(operator)); - } else { - entries.add(entry); - } - } - yield new PlayerInfoPacket(action, entries); - } - default -> this; - }; - } - - public enum Action { - ADD_PLAYER(AddPlayer.class), - UPDATE_GAMEMODE(UpdateGameMode.class), - UPDATE_LATENCY(UpdateLatency.class), - UPDATE_DISPLAY_NAME(UpdateDisplayName.class), - REMOVE_PLAYER(RemovePlayer.class); - - private final Class clazz; - - Action(Class clazz) { - this.clazz = clazz; - } - - @NotNull - public Class getClazz() { - return clazz; - } - } - - public sealed interface Entry extends NetworkBuffer.Writer - permits AddPlayer, UpdateGameMode, UpdateLatency, UpdateDisplayName, RemovePlayer { - UUID uuid(); - } - - public record AddPlayer(UUID uuid, String name, List properties, GameMode gameMode, int ping, - @Nullable Component displayName, - @Nullable PlayerPublicKey playerPublicKey) implements Entry, ComponentHolder { - public AddPlayer { - properties = List.copyOf(properties); - } - - public AddPlayer(UUID uuid, NetworkBuffer reader) { - this(uuid, reader.read(STRING), - reader.readCollection(Property::new), - GameMode.values()[reader.read(VAR_INT)], reader.read(VAR_INT), - reader.read(BOOLEAN) ? reader.read(COMPONENT) : null, - reader.read(BOOLEAN) ? new PlayerPublicKey(reader) : null); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(STRING, name); - writer.writeCollection(properties); - writer.write(VAR_INT, (int) gameMode.id()); - writer.write(VAR_INT, ping); - writer.writeOptional(COMPONENT, displayName); - writer.writeOptional(playerPublicKey); - } - - @Override - public @NotNull Collection components() { - return displayName != null ? List.of(displayName) : List.of(); - } - - @Override - public @NotNull AddPlayer copyWithOperator(@NotNull UnaryOperator operator) { - return displayName != null ? - new AddPlayer(uuid, name, properties, gameMode, ping, operator.apply(displayName), playerPublicKey) : this; - } - - public record Property(@NotNull String name, @NotNull String value, - @Nullable String signature) implements NetworkBuffer.Writer { - public Property(String name, String value) { - this(name, value, null); - } - - public Property(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), reader.read(STRING), - reader.read(BOOLEAN) ? reader.read(STRING) : null); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(STRING, name); - writer.write(STRING, value); - writer.write(BOOLEAN, signature != null); - if (signature != null) writer.write(STRING, signature); - } - } - } - - public record UpdateGameMode(UUID uuid, GameMode gameMode) implements Entry { - public UpdateGameMode(UUID uuid, NetworkBuffer reader) { - this(uuid, GameMode.fromId(reader.read(VAR_INT).byteValue())); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, (int) gameMode.id()); - } - } - - public record UpdateLatency(UUID uuid, int ping) implements Entry { - public UpdateLatency(UUID uuid, NetworkBuffer reader) { - this(uuid, reader.read(VAR_INT)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, ping); - } - } - - public record UpdateDisplayName(@NotNull UUID uuid, - @Nullable Component displayName) implements Entry, ComponentHolder { - public UpdateDisplayName(UUID uuid, NetworkBuffer reader) { - this(uuid, reader.read(BOOLEAN) ? reader.read(COMPONENT) : null); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(BOOLEAN, displayName != null); - if (displayName != null) writer.write(COMPONENT, displayName); - } - - @Override - public @NotNull Collection components() { - return displayName != null ? List.of(displayName) : List.of(); - } - - @Override - public @NotNull UpdateDisplayName copyWithOperator(@NotNull UnaryOperator operator) { - return displayName != null ? new UpdateDisplayName(uuid, operator.apply(displayName)) : this; - } - } - - public record RemovePlayer(@NotNull UUID uuid) implements Entry { - @Override - public void write(@NotNull NetworkBuffer writer) { - } - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoRemovePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoRemovePacket.java new file mode 100644 index 000000000..3eb2d15db --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoRemovePacket.java @@ -0,0 +1,33 @@ +package net.minestom.server.network.packet.server.play; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.UUID; + +public record PlayerInfoRemovePacket(@NotNull List<@NotNull UUID> uuids) implements ServerPacket { + public PlayerInfoRemovePacket(@NotNull UUID uuid) { + this(List.of(uuid)); + } + + public PlayerInfoRemovePacket { + uuids = List.copyOf(uuids); + } + + public PlayerInfoRemovePacket(@NotNull NetworkBuffer reader) { + this(reader.readCollection(NetworkBuffer.UUID)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeCollection(NetworkBuffer.UUID, uuids); + } + + @Override + public int getId() { + return ServerPacketIdentifier.PLAYER_INFO_REMOVE; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java new file mode 100644 index 000000000..1d03a5470 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java @@ -0,0 +1,154 @@ +package net.minestom.server.network.packet.server.play; + +import net.kyori.adventure.text.Component; +import net.minestom.server.crypto.ChatSession; +import net.minestom.server.entity.GameMode; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import static net.minestom.server.network.NetworkBuffer.*; + +public final class PlayerInfoUpdatePacket implements ServerPacket { + private final @NotNull EnumSet<@NotNull Action> actions; + private final @NotNull List<@NotNull Entry> entries; + + public PlayerInfoUpdatePacket(@NotNull EnumSet<@NotNull Action> actions, @NotNull List<@NotNull Entry> entries) { + this.actions = EnumSet.copyOf(actions); + this.entries = List.copyOf(entries); + } + + public PlayerInfoUpdatePacket(@NotNull Action action, @NotNull Entry entry) { + this.actions = EnumSet.of(action); + this.entries = List.of(entry); + } + + public PlayerInfoUpdatePacket(@NotNull NetworkBuffer reader) { + this.actions = reader.readEnumSet(Action.class); + this.entries = reader.readCollection(buffer -> { + UUID uuid = buffer.read(NetworkBuffer.UUID); + String username = ""; + List properties = List.of(); + boolean listed = false; + int latency = 0; + GameMode gameMode = GameMode.SURVIVAL; + Component displayName = null; + ChatSession chatSession = null; + for (Action action : actions) { + switch (action) { + case ADD_PLAYER -> { + username = reader.read(STRING); + properties = reader.readCollection(Property::new); + } + case INITIALIZE_CHAT -> chatSession = new ChatSession(reader); + case UPDATE_GAME_MODE -> gameMode = reader.readEnum(GameMode.class); + case UPDATE_LISTED -> listed = reader.read(BOOLEAN); + case UPDATE_LATENCY -> latency = reader.read(VAR_INT); + case UPDATE_DISPLAY_NAME -> displayName = reader.read(COMPONENT); + } + } + return new Entry(uuid, username, properties, listed, latency, gameMode, displayName, chatSession); + }); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeEnumSet(actions, Action.class); + writer.writeCollection(entries, (buffer, entry) -> { + buffer.write(NetworkBuffer.UUID, entry.uuid); + for (Action action : actions) { + action.writer.write(buffer, entry); + } + }); + } + + @Override + public int getId() { + return ServerPacketIdentifier.PLAYER_INFO_UPDATE; + } + + public @NotNull EnumSet actions() { + return actions; + } + + public @NotNull List entries() { + return entries; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PlayerInfoUpdatePacket that = (PlayerInfoUpdatePacket) o; + return actions.equals(that.actions) && entries.equals(that.entries); + } + + @Override + public int hashCode() { + return Objects.hash(actions, entries); + } + + @Override + public String toString() { + return "PlayerInfoUpdatePacket{" + + "actions=" + actions + + ", entries=" + entries + + '}'; + } + + public record Entry(UUID uuid, String username, List properties, + boolean listed, int latency, GameMode gameMode, + @Nullable Component displayName, @Nullable ChatSession chatSession) { + public Entry { + properties = List.copyOf(properties); + } + } + + public record Property(@NotNull String name, @NotNull String value, + @Nullable String signature) implements NetworkBuffer.Writer { + public Property(@NotNull String name, @NotNull String value) { + this(name, value, null); + } + + public Property(@NotNull NetworkBuffer reader) { + this(reader.read(STRING), reader.read(STRING), + reader.readOptional(STRING)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(STRING, name); + writer.write(STRING, value); + writer.writeOptional(STRING, signature); + } + } + + public enum Action { + ADD_PLAYER((writer, entry) -> { + writer.write(STRING, entry.username); + writer.writeCollection(entry.properties); + }), + INITIALIZE_CHAT((writer, entry) -> writer.writeOptional(entry.chatSession)), + UPDATE_GAME_MODE((writer, entry) -> writer.write(VAR_INT, entry.gameMode.ordinal())), + UPDATE_LISTED((writer, entry) -> writer.write(BOOLEAN, entry.listed)), + UPDATE_LATENCY((writer, entry) -> writer.write(VAR_INT, entry.latency)), + UPDATE_DISPLAY_NAME((writer, entry) -> writer.writeOptional(COMPONENT, entry.displayName)); + + final Writer writer; + + Action(Writer writer) { + this.writer = writer; + } + + interface Writer { + void write(NetworkBuffer writer, Entry entry); + } + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java index 35c7ad6d7..797185444 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java @@ -10,17 +10,16 @@ import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; public record ServerDataPacket(@Nullable Component motd, @Nullable String iconBase64, - boolean previewsChat, boolean enforcesSecureChat) implements ServerPacket { + boolean enforcesSecureChat) implements ServerPacket { public ServerDataPacket(@NotNull NetworkBuffer reader) { this(reader.readOptional(COMPONENT), reader.readOptional(STRING), - reader.read(BOOLEAN), reader.read(BOOLEAN)); + reader.read(BOOLEAN)); } @Override public void write(@NotNull NetworkBuffer writer) { writer.writeOptional(COMPONENT, this.motd); writer.writeOptional(STRING, this.iconBase64); - writer.write(BOOLEAN, previewsChat); writer.write(BOOLEAN, enforcesSecureChat); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SetChatPreviewPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SetChatPreviewPacket.java deleted file mode 100644 index 3bcae23e9..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/SetChatPreviewPacket.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.BOOLEAN; - -public record SetChatPreviewPacket(boolean enable) implements ServerPacket { - public SetChatPreviewPacket(@NotNull NetworkBuffer reader) { - this(reader.read(BOOLEAN)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(BOOLEAN, enable); - } - - @Override - public int getId() { - return ServerPacketIdentifier.SET_DISPLAY_CHAT_PREVIEW; - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java index efa75ce7a..a52f5a99c 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java @@ -8,27 +8,78 @@ import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.sound.SoundEvent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; -public record SoundEffectPacket(int soundId, @NotNull Source source, - int x, int y, int z, - float volume, float pitch, long seed) implements ServerPacket { - public SoundEffectPacket(@NotNull NetworkBuffer reader) { - this(reader.read(VAR_INT), reader.readEnum(Source.class), - reader.read(INT) * 8, reader.read(INT) * 8, reader.read(INT) * 8, - reader.read(FLOAT), reader.read(FLOAT), reader.read(LONG)); +public record SoundEffectPacket( + // only one of soundEvent and soundName may be present + @Nullable SoundEvent soundEvent, + @Nullable String soundName, + @Nullable Float range, // Only allowed with soundName + @NotNull Source source, + int x, + int y, + int z, + float volume, + float pitch, + long seed +) implements ServerPacket { + private static @NotNull SoundEffectPacket fromReader(@NotNull NetworkBuffer reader) { + int soundId = reader.read(VAR_INT); + SoundEvent soundEvent; + String soundName; + Float range = null; + if (soundId == 0) { + soundEvent = null; + soundName = reader.read(STRING); + range = reader.readOptional(FLOAT); + } else { + soundEvent = SoundEvent.fromId(soundId - 1); + soundName = null; + } + return new SoundEffectPacket( + soundEvent, + soundName, + range, + reader.readEnum(Source.class), + reader.read(INT) * 8, + reader.read(INT) * 8, + reader.read(INT) * 8, + reader.read(FLOAT), + reader.read(FLOAT), + reader.read(LONG) + ); } - public SoundEffectPacket(@NotNull SoundEvent sound, @NotNull Source source, - @NotNull Point position, float volume, float pitch) { - this(sound.id(), source, (int) position.x(), (int) position.y(), (int) position.z(), - volume, pitch, 0); + public SoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Source source, + @NotNull Point position, float volume, float pitch, long seed) { + this(soundEvent, null, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed); + } + + public SoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Source source, + @NotNull Point position, float volume, float pitch, long seed) { + this(null, soundName, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed); + } + + public SoundEffectPacket(@NotNull NetworkBuffer reader) { + this(fromReader(reader)); + } + + private SoundEffectPacket(@NotNull SoundEffectPacket packet) { + this(packet.soundEvent, packet.soundName, packet.range, packet.source, + packet.x, packet.y, packet.z, packet.volume, packet.pitch, packet.seed); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, soundId); + if (soundEvent != null) { + writer.write(VAR_INT, soundEvent.id() + 1); + } else { + writer.write(VAR_INT, 0); + writer.write(STRING, soundName); + writer.writeOptional(FLOAT, range); + } writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(INT, x * 8); writer.write(INT, y * 8); @@ -42,4 +93,5 @@ public record SoundEffectPacket(int soundId, @NotNull Source source, public int getId() { return ServerPacketIdentifier.SOUND_EFFECT; } + } diff --git a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java index ead02aaa7..aa9788a68 100644 --- a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java +++ b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java @@ -4,9 +4,12 @@ import com.google.gson.JsonObject; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Vec; +import net.minestom.server.crypto.ChatSession; +import net.minestom.server.crypto.PlayerPublicKey; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Metadata; +import net.minestom.server.entity.PlayerSkin; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.network.packet.client.ClientPacket; @@ -19,11 +22,18 @@ import net.minestom.server.network.packet.server.login.SetCompressionPacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.packet.server.play.DeclareRecipesPacket.Ingredient; import net.minestom.server.network.packet.server.status.PongPacket; +import net.minestom.server.utils.crypto.KeyUtils; +import org.apache.commons.net.util.Base64; import org.jglrxavpok.hephaistos.nbt.NBT; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -110,18 +120,20 @@ public class PacketWriteReadTest { SERVER_PACKETS.add(new EntityPropertiesPacket(5, List.of())); SERVER_PACKETS.add(new EntityRotationPacket(5, 45f, 45f, false)); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - new PlayerInfoPacket.UpdateDisplayName(UUID.randomUUID(), COMPONENT))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - new PlayerInfoPacket.UpdateDisplayName(UUID.randomUUID(), (Component) null))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE, - new PlayerInfoPacket.UpdateGameMode(UUID.randomUUID(), GameMode.CREATIVE))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY, - new PlayerInfoPacket.UpdateLatency(UUID.randomUUID(), 5))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER, - new PlayerInfoPacket.AddPlayer(UUID.randomUUID(), "TheMode911", - List.of(new PlayerInfoPacket.AddPlayer.Property("name", "value")), GameMode.CREATIVE, 5, COMPONENT, null))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER, new PlayerInfoPacket.RemovePlayer(UUID.randomUUID()))); + final PlayerSkin skin = new PlayerSkin("hh", "hh"); + List prop = List.of(new PlayerInfoUpdatePacket.Property("textures", skin.textures(), skin.signature())); + + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.ADD_PLAYER, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "TheMode911", prop, false, 0, GameMode.SURVIVAL, null, null))); +// SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, +// new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), false, 0, GameMode.SURVIVAL, Component.text("NotTheMode911"), null))); + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), false, 0, GameMode.CREATIVE, null, null))); + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), false, 20, GameMode.SURVIVAL, null, null))); + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LISTED, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), true, 0, GameMode.SURVIVAL, null, null))); + SERVER_PACKETS.add(new PlayerInfoRemovePacket(UUID.randomUUID())); //SERVER_PACKETS.add(new MultiBlockChangePacket(5,5,5,true, new long[]{0,5,543534,1321})); }