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 6f8f2525f..1ae4dc237 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 @@ package org.geysermc.geyser.entity; 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 class GeyserEntityData implements EntityData { 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 e7cf81d47..ff330e771 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.MobArmorEquipment 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.ClientCacheStatusPacket; 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.ScriptMessagePacket; 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 @@ class CodecProcessor { } }; + 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 @@ class CodecProcessor { } }; - /** - * 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 @@ class CodecProcessor { .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 @@ class CodecProcessor { .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 910f76ffb..d1ae569e7 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 @@ import org.geysermc.geyser.session.GeyserSession; 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 000000000..bf5ae2b22 --- /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 f56a8a43f..caa6c6bf9 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.CompressionStra 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 org.geysermc.geyser.util.VersionCheckUtils; 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 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override PacketSignal defaultHandler(BedrockPacket packet) { + this.cooldownHandler.handle(packet); return translateAndDefault(packet); } @@ -150,6 +149,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public PacketSignal handle(RequestNetworkSettingsPacket packet) { + this.cooldownHandler.handle(packet); if (!setCorrectCodec(packet.getProtocolVersion())) { return PacketSignal.HANDLED; } @@ -169,6 +169,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @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 class UpstreamPacketHandler extends LoggingPacketHandler { @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 class UpstreamPacketHandler extends LoggingPacketHandler { 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 class UpstreamPacketHandler extends LoggingPacketHandler { @Override public PacketSignal handle(ModalFormResponsePacket packet) { + this.cooldownHandler.handle(packet); session.executeInEventLoop(() -> session.getFormCache().handleResponse(packet)); return PacketSignal.HANDLED; } @@ -308,8 +331,13 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @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 class UpstreamPacketHandler extends LoggingPacketHandler { 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 class UpstreamPacketHandler extends LoggingPacketHandler { 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 a67bd8a32..571e0f667 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 @@ public final class GeyserServer { .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 9a990865e..2691f0b99 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -118,7 +118,6 @@ import org.geysermc.geyser.api.network.AuthType; 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; @@ -346,7 +345,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Exposed for GeyserConnect usage protected boolean sentSpawnPacket; + @Getter private boolean loggedIn; + @Getter private boolean loggingIn; @Setter @@ -587,8 +588,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. */ @@ -676,13 +675,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { 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(); } @@ -1938,23 +1930,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { 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 3f7df97c1..f14eac28f 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 class FormCache { 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 1e84f032e..b8f430b17 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 @@ -39,8 +39,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()); - } -}