diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java index c9ef7a2dda2..bade88f1fc2 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java @@ -28,6 +28,7 @@ import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.protocol.bedrock.data.EmoteFlag; import org.cloudburstmc.protocol.bedrock.packet.EmotePacket; import org.geysermc.geyser.api.entity.EntityData; import org.geysermc.geyser.api.entity.type.GeyserEntity; @@ -71,6 +72,8 @@ public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteI packet.setXuid(""); packet.setPlatformId(""); // BDS sends empty packet.setEmoteId(emoteId); + packet.getFlags().add(EmoteFlag.SERVER_SIDE); + packet.getFlags().add(EmoteFlag.MUTE_EMOTE_CHAT); session.sendUpstreamPacket(packet); } diff --git a/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java b/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java index e7cf81d4799..ff330e77194 100644 --- a/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java +++ b/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java @@ -33,13 +33,20 @@ import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.MobEquipmentSerializer_v291; import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.PlayerHotbarSerializer_v291; import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityLinkSerializer_v291; -import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityMotionSerializer_v291; import org.cloudburstmc.protocol.bedrock.codec.v390.serializer.PlayerSkinSerializer_v390; import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventoryContentSerializer_v407; import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventorySlotSerializer_v407; +import org.cloudburstmc.protocol.bedrock.codec.v422.serializer.FilterTextSerializer_v422; import org.cloudburstmc.protocol.bedrock.codec.v486.serializer.BossEventSerializer_v486; +import org.cloudburstmc.protocol.bedrock.codec.v554.serializer.TextSerializer_v554; import org.cloudburstmc.protocol.bedrock.codec.v557.serializer.SetEntityDataSerializer_v557; +import org.cloudburstmc.protocol.bedrock.codec.v567.serializer.CommandRequestSerializer_v567; +import org.cloudburstmc.protocol.bedrock.codec.v630.serializer.SetPlayerInventoryOptionsSerializer_v360; import org.cloudburstmc.protocol.bedrock.codec.v662.serializer.SetEntityMotionSerializer_v662; +import org.cloudburstmc.protocol.bedrock.codec.v685.serializer.TextSerializer_v685; +import org.cloudburstmc.protocol.bedrock.data.inventory.InventoryLayout; +import org.cloudburstmc.protocol.bedrock.data.inventory.InventoryTabLeft; +import org.cloudburstmc.protocol.bedrock.data.inventory.InventoryTabRight; import org.cloudburstmc.protocol.bedrock.packet.AnvilDamagePacket; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.cloudburstmc.protocol.bedrock.packet.BossEventPacket; @@ -48,11 +55,14 @@ import org.cloudburstmc.protocol.bedrock.packet.ClientCheatAbilityPacket; import org.cloudburstmc.protocol.bedrock.packet.ClientToServerHandshakePacket; import org.cloudburstmc.protocol.bedrock.packet.CodeBuilderSourcePacket; +import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket; import org.cloudburstmc.protocol.bedrock.packet.CraftingEventPacket; import org.cloudburstmc.protocol.bedrock.packet.CreatePhotoPacket; import org.cloudburstmc.protocol.bedrock.packet.DebugInfoPacket; import org.cloudburstmc.protocol.bedrock.packet.EditorNetworkPacket; +import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket; import org.cloudburstmc.protocol.bedrock.packet.EntityFallPacket; +import org.cloudburstmc.protocol.bedrock.packet.FilterTextPacket; import org.cloudburstmc.protocol.bedrock.packet.GameTestRequestPacket; import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; @@ -74,10 +84,12 @@ import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetPlayerInventoryOptionsPacket; import org.cloudburstmc.protocol.bedrock.packet.SettingsCommandPacket; import org.cloudburstmc.protocol.bedrock.packet.SimpleEventPacket; import org.cloudburstmc.protocol.bedrock.packet.SubChunkRequestPacket; import org.cloudburstmc.protocol.bedrock.packet.SubClientLoginPacket; +import org.cloudburstmc.protocol.bedrock.packet.TextPacket; import org.cloudburstmc.protocol.bedrock.packet.TickSyncPacket; import org.cloudburstmc.protocol.common.util.VarInts; @@ -136,6 +148,84 @@ public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlot } }; + private static final BedrockPacketSerializer SET_PLAYER_INVENTORY_OPTIONS_SERIALIZER = new SetPlayerInventoryOptionsSerializer_v360() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetPlayerInventoryOptionsPacket packet) { + int leftTabIndex = VarInts.readInt(buffer); + int rightTabIndex = VarInts.readInt(buffer); + + packet.setLeftTab(leftTabIndex >= 0 && leftTabIndex < InventoryTabLeft.VALUES.length ? InventoryTabLeft.VALUES[leftTabIndex] : InventoryTabLeft.NONE); + packet.setRightTab(rightTabIndex >= 0 && rightTabIndex < InventoryTabRight.VALUES.length ? InventoryTabRight.VALUES[rightTabIndex] : InventoryTabRight.NONE); + + packet.setFiltering(buffer.readBoolean()); + + int layoutIndex = VarInts.readInt(buffer); + packet.setLayout(layoutIndex >= 0 && layoutIndex < InventoryLayout.VALUES.length ? InventoryLayout.VALUES[layoutIndex] : InventoryLayout.NONE); + + int craftingLayoutIndex = VarInts.readInt(buffer); + packet.setCraftingLayout(craftingLayoutIndex >= 0 && craftingLayoutIndex < InventoryLayout.VALUES.length ? InventoryLayout.VALUES[craftingLayoutIndex] : InventoryLayout.NONE); + } + }; + + private static final BedrockPacketSerializer FILTER_TEXT = new FilterTextSerializer_v422() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, FilterTextPacket packet) { + packet.setText(helper.readStringMaxLen(buffer, 513)); + packet.setFromServer(buffer.readBoolean()); + } + }; + + private static final BedrockPacketSerializer COMMAND_REQUEST_SERIALIZER = new CommandRequestSerializer_v567() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, CommandRequestPacket packet) { + packet.setCommand(helper.readStringMaxLen(buffer, 513)); + packet.setCommandOriginData(helper.readCommandOrigin(buffer)); + packet.setInternal(buffer.readBoolean()); + packet.setVersion(VarInts.readInt(buffer)); + } + }; + + private static final BedrockPacketSerializer TEXT_SERIALIZER_V554 = new TextSerializer_v554() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, TextPacket packet) { + TextPacket.Type type = TextPacket.Type.values()[buffer.readUnsignedByte()]; + packet.setType(type); + packet.setNeedsTranslation(buffer.readBoolean()); + + if (type == TextPacket.Type.CHAT) { + packet.setSourceName(helper.readString(buffer)); + //The client does not send more than 512 characters, and we do not need to decode other TextPacket.Type + packet.setMessage(helper.readStringMaxLen(buffer, 513)); + } else { + throw new IllegalArgumentException("Unsupported TextType " + type); + } + + packet.setXuid(helper.readString(buffer)); + packet.setPlatformChatId(helper.readString(buffer)); + } + }; + + private static final BedrockPacketSerializer TEXT_SERIALIZER = new TextSerializer_v685() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, TextPacket packet) { + TextPacket.Type type = TextPacket.Type.values()[buffer.readUnsignedByte()]; + packet.setType(type); + packet.setNeedsTranslation(buffer.readBoolean()); + + if (type == TextPacket.Type.CHAT) { + packet.setSourceName(helper.readString(buffer)); + //The client does not send more than 512 characters, and we do not need to decode other TextPacket.Type + packet.setMessage(helper.readStringMaxLen(buffer, 513)); + } else { + throw new IllegalArgumentException("Unsupported TextType " + type); + } + + packet.setXuid(helper.readString(buffer)); + packet.setPlatformChatId(helper.readString(buffer)); + packet.setFilteredMessage(helper.readString(buffer)); + } + }; + /** * Serializer that does nothing when trying to deserialize BossEventPacket since it is not used from the client. */ @@ -181,15 +271,6 @@ public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityData } }; - /** - * Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v291. - */ - private static final BedrockPacketSerializer SET_ENTITY_MOTION_SERIALIZER_V291 = new SetEntityMotionSerializer_v291() { - @Override - public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) { - } - }; - /** * Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v662. */ @@ -251,6 +332,7 @@ static BedrockCodec processCodec(BedrockCodec codec) { .updateSerializer(SettingsCommandPacket.class, IGNORED_SERIALIZER) .updateSerializer(AnvilDamagePacket.class, IGNORED_SERIALIZER) .updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER) + .updateSerializer(EmoteListPacket.class, IGNORED_SERIALIZER) // Illegal when serverbound due to Geyser specific setup .updateSerializer(InventoryContentPacket.class, INVENTORY_CONTENT_SERIALIZER) .updateSerializer(InventorySlotPacket.class, INVENTORY_SLOT_SERIALIZER) @@ -273,10 +355,19 @@ static BedrockCodec processCodec(BedrockCodec codec) { .updateSerializer(SimpleEventPacket.class, IGNORED_SERIALIZER) .updateSerializer(MultiplayerSettingsPacket.class, IGNORED_SERIALIZER); + if (codec.getProtocolVersion() < 685) { // Ignored bidirectional packets codecBuilder.updateSerializer(TickSyncPacket.class, IGNORED_SERIALIZER); } + codecBuilder.updateSerializer(FilterTextPacket.class, FILTER_TEXT); + codecBuilder.updateSerializer(CommandRequestPacket.class, COMMAND_REQUEST_SERIALIZER); + codecBuilder.updateSerializer(SetPlayerInventoryOptionsPacket.class, SET_PLAYER_INVENTORY_OPTIONS_SERIALIZER); + if (codec.getProtocolVersion() >= 685) { + codecBuilder.updateSerializer(TextPacket.class, TEXT_SERIALIZER); + } else { + codecBuilder.updateSerializer(TextPacket.class, TEXT_SERIALIZER_V554); + } return codecBuilder.build(); } diff --git a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java index 910f76ffb7b..d1ae569e741 100644 --- a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java @@ -39,13 +39,16 @@ public class LoggingPacketHandler implements BedrockPacketHandler { protected final GeyserImpl geyser; protected final GeyserSession session; + protected final PacketCooldownManager cooldownHandler; LoggingPacketHandler(GeyserImpl geyser, GeyserSession session) { this.geyser = geyser; this.session = session; + this.cooldownHandler = new PacketCooldownManager(session); } PacketSignal defaultHandler(BedrockPacket packet) { + this.cooldownHandler.handle(packet); geyser.getLogger().debug("Handled packet: " + packet.getClass().getSimpleName()); return PacketSignal.HANDLED; } diff --git a/core/src/main/java/org/geysermc/geyser/network/PacketCooldownManager.java b/core/src/main/java/org/geysermc/geyser/network/PacketCooldownManager.java new file mode 100644 index 00000000000..bf5ae2b22a1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/PacketCooldownManager.java @@ -0,0 +1,106 @@ +package org.geysermc.geyser.network; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; +import lombok.Setter; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket; +import org.cloudburstmc.protocol.bedrock.packet.BlockPickRequestPacket; +import org.cloudburstmc.protocol.bedrock.packet.BookEditPacket; +import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket; +import org.cloudburstmc.protocol.bedrock.packet.EntityPickRequestPacket; +import org.cloudburstmc.protocol.bedrock.packet.FilterTextPacket; +import org.cloudburstmc.protocol.bedrock.packet.LecternUpdatePacket; +import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; +import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket; +import org.cloudburstmc.protocol.bedrock.packet.RequestNetworkSettingsPacket; +import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkRequestPacket; +import org.cloudburstmc.protocol.bedrock.packet.ResourcePackClientResponsePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket; +import org.cloudburstmc.protocol.bedrock.packet.TextPacket; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Map; + +public class PacketCooldownManager { + + private final GeyserSession session; + private final Map packetCooldownSettings = new Object2ObjectOpenHashMap<>(); + private final Map activeCooldowns = new Object2ObjectOpenHashMap<>(); + + public PacketCooldownManager(GeyserSession session) { + this.session = session; + + setPacketCooldown(LoginPacket.class, -1, 2); + setPacketCooldown(ResourcePackClientResponsePacket.class, -1, 4); + setPacketCooldown(ResourcePackChunkRequestPacket.class, -1, 0); + setPacketCooldown(RequestNetworkSettingsPacket.class, -1, 2); + setPacketCooldown(TextPacket.class, 1000, 50); + setPacketCooldown(CommandRequestPacket.class, 1000, 50); + setPacketCooldown(ModalFormResponsePacket.class, 1000, 50); + setPacketCooldown(BlockEntityDataPacket.class, 1000, 40); + setPacketCooldown(SetLocalPlayerAsInitializedPacket.class, 1000, 40); + setPacketCooldown(BlockPickRequestPacket.class, 1000, 40); + setPacketCooldown(EntityPickRequestPacket.class, 1000, 40); + setPacketCooldown(BookEditPacket.class, 1000, 40); + setPacketCooldown(FilterTextPacket.class, 1000, 40); + setPacketCooldown(LecternUpdatePacket.class, 1000, 10); + } + + public void setPacketCooldown(Class packetClass, int cooldownMillis, int maxCount) { + packetCooldownSettings.put(packetClass.getSimpleName(), new CooldownSettings(cooldownMillis, maxCount)); + } + + private boolean isCooldown(BedrockPacket packet) { + String packetName = packet.getClass().getSimpleName(); + CooldownSettings settings = packetCooldownSettings.get(packetName); + if (settings == null) return false; + + CooldownTracker tracker = activeCooldowns.computeIfAbsent(packetName, k -> { + CooldownTracker newTracker = new CooldownTracker(); + long cooldownMillis = settings.cooldownMillis(); + if (cooldownMillis == -1) { + newTracker.setExpiryTime(-1); + } else { + newTracker.setExpiryTime(System.currentTimeMillis() + cooldownMillis); + } + return newTracker; + }); + tracker.incrementCount(); + + if (tracker.getExpiryTime() != -1 && tracker.getExpiryTime() <= System.currentTimeMillis()) { + activeCooldowns.remove(packetName); + return false; + } + + return tracker.getCount() >= settings.maxCount(); + } + + public void handle(BedrockPacket packet) { + String packetName = packet.getClass().getSimpleName(); + if (!isCooldown(packet)) return; + if (session.getGeyser().getConfig().isDebugMode()) { + CooldownTracker tracker = activeCooldowns.get(packetName); + String message = session.getSocketAddress().getAddress().toString() + " -> Attempted to send too many packets " + packetName + " count " + tracker.getCount(); + if (session.isLoggedIn()) { + message += " by user " + session.bedrockUsername(); + } + session.getGeyser().getLogger().debug(message); + } + session.disconnect("many Packets " + packetName); + } + + private record CooldownSettings(int cooldownMillis, int maxCount) { + } + + @Getter + private class CooldownTracker { + private long count; + @Setter + private long expiryTime; + + public void incrementCount() { + this.count++; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index c7aabb8068c..577af02defc 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -37,6 +37,7 @@ import org.cloudburstmc.protocol.bedrock.netty.codec.compression.SimpleCompressionStrategy; import org.cloudburstmc.protocol.bedrock.netty.codec.compression.ZlibCompression; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.cloudburstmc.protocol.bedrock.packet.LecternUpdatePacket; import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; @@ -73,15 +74,12 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; -import java.util.ArrayDeque; -import java.util.Deque; import java.util.HashMap; import java.util.OptionalInt; public class UpstreamPacketHandler extends LoggingPacketHandler { private boolean networkSettingsRequested = false; - private final Deque packsToSent = new ArrayDeque<>(); private final CompressionStrategy compressionStrategy; private SessionLoadResourcePacksEventImpl resourcePackLoadEvent; @@ -101,6 +99,7 @@ private PacketSignal translateAndDefault(BedrockPacket packet) { @Override PacketSignal defaultHandler(BedrockPacket packet) { + this.cooldownHandler.handle(packet); return translateAndDefault(packet); } @@ -150,6 +149,7 @@ public void onDisconnect(String reason) { @Override public PacketSignal handle(RequestNetworkSettingsPacket packet) { + this.cooldownHandler.handle(packet); if (!setCorrectCodec(packet.getProtocolVersion())) { return PacketSignal.HANDLED; } @@ -169,6 +169,7 @@ public PacketSignal handle(RequestNetworkSettingsPacket packet) { @Override public PacketSignal handle(LoginPacket loginPacket) { + this.cooldownHandler.handle(loginPacket); if (geyser.isShuttingDown() || geyser.isReloading()) { // Don't allow new players in if we're no longer operating session.disconnect(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.message")); @@ -220,6 +221,11 @@ public PacketSignal handle(LoginPacket loginPacket) { @Override public PacketSignal handle(ResourcePackClientResponsePacket packet) { + this.cooldownHandler.handle(packet); + if (packet.getPackIds().size() > this.resourcePackLoadEvent.getPacks().size()) { + session.disconnect("Packet " + packet.getClass().getSimpleName() + " PackIds max count"); + return PacketSignal.HANDLED; + } switch (packet.getStatus()) { case COMPLETED: if (geyser.getConfig().getRemote().authType() != AuthType.ONLINE) { @@ -232,8 +238,24 @@ public PacketSignal handle(ResourcePackClientResponsePacket packet) { break; case SEND_PACKS: - packsToSent.addAll(packet.getPackIds()); - sendPackDataInfo(packsToSent.pop()); + int chunkIndex = 1; + for (String id : packet.getPackIds()) { + + String[] packID = id.split("_", 2); + if (packID.length != 2) { + session.disconnect("Invalid packID id: " + id); + break; + } + ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]); + if (pack == null) { + session.disconnect("Invalid request unknown pack " + packID[0] + ", available packs: " + this.resourcePackLoadEvent.getPacks().keySet()); + break; + } + + sendPackDataInfo(id); + chunkIndex += (int) ((this.resourcePackLoadEvent.getPacks().get(packID[0]).codec().size() + GeyserResourcePack.CHUNK_SIZE) / GeyserResourcePack.CHUNK_SIZE); + } + cooldownHandler.setPacketCooldown(ResourcePackChunkRequestPacket.class, -1, chunkIndex); break; case HAVE_ALL_PACKS: @@ -268,6 +290,7 @@ public PacketSignal handle(ResourcePackClientResponsePacket packet) { @Override public PacketSignal handle(ModalFormResponsePacket packet) { + this.cooldownHandler.handle(packet); session.executeInEventLoop(() -> session.getFormCache().handleResponse(packet)); return PacketSignal.HANDLED; } @@ -308,8 +331,13 @@ public PacketSignal handle(MovePlayerPacket packet) { @Override public PacketSignal handle(ResourcePackChunkRequestPacket packet) { + this.cooldownHandler.handle(packet); ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket(); ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packet.getPackId().toString()); + if (pack == null) { + session.disconnect("Invalid request for chunk " + packet.getChunkIndex() + " of unknown pack " + packet.getPackId() + ", available packs: " + this.resourcePackLoadEvent.getPacks().keySet()); + return PacketSignal.HANDLED; + } PackCodec codec = pack.codec(); data.setChunkIndex(packet.getChunkIndex()); @@ -318,6 +346,10 @@ public PacketSignal handle(ResourcePackChunkRequestPacket packet) { data.setPackId(packet.getPackId()); int offset = packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE; + if (offset < 0 || offset >= codec.size()) { + session.disconnect("Invalid out-of-bounds request for chunk " + packet.getChunkIndex() + " of " + packet.getPackId() + " offset " + offset + ", file size " + codec.size()); + return PacketSignal.HANDLED; + } long remainingSize = codec.size() - offset; byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, GeyserResourcePack.CHUNK_SIZE)]; @@ -332,17 +364,12 @@ public PacketSignal handle(ResourcePackChunkRequestPacket packet) { session.sendUpstreamPacket(data); - // Check if it is the last chunk and send next pack in queue when available. - if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) { - sendPackDataInfo(packsToSent.pop()); - } - return PacketSignal.HANDLED; } private void sendPackDataInfo(String id) { ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket(); - String[] packID = id.split("_"); + String[] packID = id.split("_", 2); ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]); PackCodec codec = pack.codec(); ResourcePackManifest.Header header = pack.manifest().header(); diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java index a67bd8a3281..571e0f66734 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java @@ -236,7 +236,7 @@ private ServerBootstrap createBootstrap() { .group(group, childGroup) .option(RakChannelOption.RAK_HANDLE_PING, true) .option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu()) - .option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit) + .option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit) // FIXME When using Velocity, the proxy server gets kicked for exceeding the limit, even if this value is increased tenfold. With an online count of 1000, this happens about 40 times a day at a value of 450. It is unclear why it detects the server's IP with Velocity, rather than the players' IPs. .option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit) .option(RakChannelOption.RAK_SEND_COOKIE, rakSendCookie) .childHandler(serverInitializer); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 25dd21662e9..61a2bad3506 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -79,7 +79,6 @@ import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.GeyserEntityData; @@ -274,7 +273,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Exposed for GeyserConnect usage protected boolean sentSpawnPacket; + @Getter private boolean loggedIn; + @Getter private boolean loggingIn; @Setter @@ -510,8 +511,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private boolean waitingForStatistics = false; - private final Set emotes; - /** * Whether advanced tooltips will be added to the player's items. */ @@ -599,13 +598,6 @@ public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSessio this.spawned = false; this.loggedIn = false; - if (geyser.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) { - this.emotes = new HashSet<>(); - geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes())); - } else { - this.emotes = null; - } - this.remoteServer = geyser.defaultRemoteServer(); } @@ -1870,23 +1862,6 @@ public void updateStatistics(@NonNull Object2IntMap statistics) { this.statistics.putAll(statistics); } - public void refreshEmotes(List emotes) { - this.emotes.addAll(emotes); - for (GeyserSession player : geyser.getSessionManager().getSessions().values()) { - List pieces = new ArrayList<>(); - for (UUID piece : emotes) { - if (!player.getEmotes().contains(piece)) { - pieces.add(piece); - } - player.getEmotes().add(piece); - } - EmoteListPacket emoteList = new EmoteListPacket(); - emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); - emoteList.getPieceIds().addAll(pieces); - player.sendUpstreamPacket(emoteList); - } - } - public boolean canUseCommandBlocks() { return instabuild && opPermissionLevel >= 2; } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java index 3f7df97c1b2..f14eac28f68 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java @@ -25,18 +25,21 @@ package org.geysermc.geyser.session.cache; -import org.cloudburstmc.protocol.bedrock.packet.ModalFormRequestPacket; -import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket; -import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; +import org.cloudburstmc.protocol.bedrock.data.ModalFormCancelReason; +import org.cloudburstmc.protocol.bedrock.packet.ModalFormRequestPacket; +import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket; +import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.SimpleForm; import org.geysermc.cumulus.form.impl.FormDefinitions; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.GeyserSession; +import java.util.LinkedList; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -52,11 +55,22 @@ public class FormCache { private final FormDefinitions formDefinitions = FormDefinitions.instance(); private final AtomicInteger formIdCounter = new AtomicInteger(0); private final Int2ObjectMap
forms = new Int2ObjectOpenHashMap<>(); + LinkedList formsOrderList = new LinkedList<>(); private final GeyserSession session; public int addForm(Form form) { int formId = formIdCounter.getAndIncrement(); forms.put(formId, form); + formsOrderList.add(formId); + + if (formsOrderList.size() > 50) { + int removeFormId = formsOrderList.getFirst(); + ModalFormResponsePacket packet = new ModalFormResponsePacket(); + packet.setFormId(removeFormId); + packet.setCancelReason(Optional.of(ModalFormCancelReason.USER_CLOSED)); + packet.setFormData(null); + this.handleResponse(packet); + } return formId; } @@ -96,6 +110,7 @@ public void resendAllForms() { public void handleResponse(ModalFormResponsePacket response) { Form form = forms.remove(response.getFormId()); + formsOrderList.remove((Integer) response.getFormId()); if (form == null) { return; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 8d4df6f3ffe..e96da1057e3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -38,8 +38,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { - - @Override - public void translate(GeyserSession session, EmoteListPacket packet) { - if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) { - return; - } - - session.refreshEmotes(packet.getPieceIds()); - } -}