From 62cbdb8f5c41b78a332f3d6100857458d3c51166 Mon Sep 17 00:00:00 2001 From: YHDiamond <47502993+YHDiamond@users.noreply.github.com> Date: Tue, 16 Feb 2021 20:09:18 -0500 Subject: [PATCH 1/6] [ci skip] Fixed if spacing (#1941) Co-authored-by: yehudahrrs <47502993+yehudahrrs@users.noreply.github.com> --- .../java/org/geysermc/platform/sponge/GeyserSpongePlugin.java | 2 +- .../geysermc/connector/entity/living/animal/RabbitEntity.java | 2 +- .../connector/entity/living/merchant/VillagerEntity.java | 2 +- .../org/geysermc/connector/network/QueryPacketHandler.java | 4 ++-- .../translators/java/world/JavaPlaySoundTranslator.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 142d48d2918..1986bbd2205 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -101,7 +101,7 @@ public void onEnable() { } } - if (geyserConfig.getBedrock().isCloneRemotePort()){ + if (geyserConfig.getBedrock().isCloneRemotePort()) { geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 544014115ce..752a0d1062f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -44,7 +44,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s if (entityMetadata.getId() == 15) { metadata.put(EntityData.SCALE, .55f); boolean isBaby = (boolean) entityMetadata.getValue(); - if(isBaby) { + if (isBaby) { metadata.put(EntityData.SCALE, .35f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index d481cd0c5ab..56354774df9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -125,7 +125,7 @@ public void moveRelative(GeyserSession session, double relX, double relY, double Pattern r = Pattern.compile("facing=([a-z]+)"); Matcher m = r.matcher(bedRotationZ); if (m.find()) { - switch (m.group(0)){ + switch (m.group(0)) { case "facing=south": //bed is facing south z = 180; diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java index 637f6d99d08..87541f70409 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -64,7 +64,7 @@ public class QueryPacketHandler { * @param buffer The Query data */ public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) { - if(!isQueryPacket(buffer)) + if (!isQueryPacket(buffer)) return; this.connector = connector; @@ -225,7 +225,7 @@ private byte[] getPlayers() { query.write(new byte[] { 0x00, 0x00 }); // Fill player names - if(pingInfo != null) { + if (pingInfo != null) { for (String username : pingInfo.getPlayerList()) { query.write(username.getBytes()); query.write((byte) 0x00); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java index 238e9ba32e9..56aa27992e4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java @@ -52,7 +52,7 @@ public void translate(ServerPlaySoundPacket packet, GeyserSession session) { SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound.replace("minecraft:", "")); String playsound; - if(soundMapping == null || soundMapping.getPlaysound() == null) { + if (soundMapping == null || soundMapping.getPlaysound() == null) { // no mapping session.getConnector().getLogger() .debug("[PlaySound] Defaulting to sound server gave us for " + packet.toString()); From fc712ea89262c2aeb66fb878680a761f3e787676 Mon Sep 17 00:00:00 2001 From: bundabrg Date: Wed, 17 Feb 2021 13:23:55 +0800 Subject: [PATCH 2/6] Fix NPE in ServerEntityAnimationPacket (#1942) Closes: https://github.com/bundabrg/GeyserReversion/issues/41 Co-authored-by: bundabrg --- .../translators/java/entity/JavaEntityAnimationTranslator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java index 53c2864c8b5..735a5ea4713 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java @@ -60,6 +60,9 @@ public void translate(ServerEntityAnimationPacket packet, GeyserSession session) case LEAVE_BED: animatePacket.setAction(AnimatePacket.Action.WAKE_UP); break; + default: + // Unknown Animation + return; } session.sendUpstreamPacket(animatePacket); From 15027c222e9b47de5d52af2376c05632dbbf68fc Mon Sep 17 00:00:00 2001 From: bundabrg Date: Wed, 17 Feb 2021 13:58:08 +0800 Subject: [PATCH 3/6] Fix NPE when a TitlePacket of type TIMES is sent (#1944) References: https://github.com/bundabrg/GeyserReversion/issues/29 Co-authored-by: bundabrg --- .../connector/network/translators/java/JavaTitleTranslator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java index d3b93068aae..ffda57826a3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java @@ -71,6 +71,7 @@ public void translate(ServerTitlePacket packet, GeyserSession session) { titlePacket.setFadeInTime(packet.getFadeIn()); titlePacket.setFadeOutTime(packet.getFadeOut()); titlePacket.setStayTime(packet.getStay()); + titlePacket.setText(""); break; } From d9f8e9eec2a8a1c89c551a0e0a80e1c4625c3157 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 17 Feb 2021 18:00:00 -0500 Subject: [PATCH 4/6] Improve command freezing and fix up command suggestion translation (#1936) Merge commands with the same parameters (thanks Supreme) Add more suggestion types (blocks, items, entities) More optimizations Co-authored-by: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Co-authored-by: rtm516 --- .../connector/entity/type/EntityType.java | 34 +- .../network/translators/item/Enchantment.java | 12 + .../translators/item/ItemRegistry.java | 11 + .../java/JavaDeclareCommandsTranslator.java | 296 ++++++++++++------ .../world/block/BlockTranslator.java | 7 + 5 files changed, 260 insertions(+), 100 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index e1e531f42f1..c45a38ad0af 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -30,8 +30,11 @@ import org.geysermc.connector.entity.living.*; import org.geysermc.connector.entity.living.animal.*; import org.geysermc.connector.entity.living.animal.horse.*; -import org.geysermc.connector.entity.living.animal.tameable.*; -import org.geysermc.connector.entity.living.merchant.*; +import org.geysermc.connector.entity.living.animal.tameable.CatEntity; +import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity; +import org.geysermc.connector.entity.living.animal.tameable.WolfEntity; +import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; +import org.geysermc.connector.entity.living.merchant.VillagerEntity; import org.geysermc.connector.entity.living.monster.*; import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; import org.geysermc.connector.entity.living.monster.raid.PillagerEntity; @@ -39,6 +42,9 @@ import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity; import org.geysermc.connector.entity.player.PlayerEntity; +import java.util.ArrayList; +import java.util.List; + @Getter public enum EntityType { @@ -174,17 +180,33 @@ public enum EntityType { */ ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand"); + /** + * A list of all Java identifiers for use with command suggestions + */ + public static final String[] ALL_JAVA_IDENTIFIERS; private static final EntityType[] VALUES = values(); - private Class entityClass; + static { + List allJavaIdentifiers = new ArrayList<>(); + for (EntityType type : values()) { + if (type == AGENT || type == BALLOON || type == CHALKBOARD || type == NPC || type == TRIPOD_CAMERA || type == ENDER_DRAGON_PART) { + continue; + } + allJavaIdentifiers.add("minecraft:" + type.name().toLowerCase()); + } + ALL_JAVA_IDENTIFIERS = allJavaIdentifiers.toArray(new String[0]); + } + + private final Class entityClass; private final int type; private final float height; private final float width; private final float length; private final float offset; - private String identifier; + private final String identifier; EntityType(Class entityClass, int type, float height) { + //noinspection SuspiciousNameCombination this(entityClass, type, height, height); } @@ -198,8 +220,6 @@ public enum EntityType { EntityType(Class entityClass, int type, float height, float width, float length, float offset) { this(entityClass, type, height, width, length, offset, null); - - this.identifier = "minecraft:" + name().toLowerCase(); } EntityType(Class entityClass, int type, float height, float width, float length, float offset, String identifier) { @@ -209,7 +229,7 @@ public enum EntityType { this.width = width; this.length = length; this.offset = offset + 0.00001f; - this.identifier = identifier; + this.identifier = identifier == null ? "minecraft:" + name().toLowerCase() : identifier; } public static EntityType getFromIdentifier(String identifier) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java index 769cbd63aea..a3b4b6c3193 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java @@ -69,6 +69,18 @@ public enum Enchantment { QUICK_CHARGE, SOUL_SPEED; + /** + * A list of all enchantment Java identifiers for use with command suggestions. + */ + public static final String[] ALL_JAVA_IDENTIFIERS; + + static { + ALL_JAVA_IDENTIFIERS = new String[values().length]; + for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { + ALL_JAVA_IDENTIFIERS[i] = values()[i].javaIdentifier; + } + } + private final String javaIdentifier; Enchantment() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index e9b821588db..9d1921731e0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -63,6 +63,11 @@ public class ItemRegistry { public static final List ITEMS = new ArrayList<>(); public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + /** + * A list of all Java item names. + */ + public static final String[] ITEM_NAMES; + /** * Bamboo item entry, used in PandaEntity.java */ @@ -116,6 +121,8 @@ public static void init() { // Used to get the Bedrock namespaced ID (in instances where there are small differences) Int2ObjectMap bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>(); + List itemNames = new ArrayList<>(); + List itemEntries; try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); @@ -207,6 +214,8 @@ public static void init() { BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); } + itemNames.add(entry.getKey()); + itemIndex++; } @@ -235,6 +244,8 @@ public static void init() { creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag())); } CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); + + ITEM_NAMES = itemNames.toArray(new String[0]); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java index f6664c1a697..7de1018111f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java @@ -33,21 +33,69 @@ import com.nukkitx.protocol.bedrock.data.command.CommandParamData; import com.nukkitx.protocol.bedrock.data.command.CommandParamType; import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket; +import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import lombok.Getter; +import lombok.ToString; +import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.Enchantment; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; @Translator(packet = ServerDeclareCommandsPacket.class) public class JavaDeclareCommandsTranslator extends PacketTranslator { + + private static final String[] ENUM_BOOLEAN = {"true", "false"}; + private static final String[] VALID_COLORS; + private static final String[] VALID_SCOREBOARD_SLOTS; + + private static final Hash.Strategy PARAM_STRATEGY = new Hash.Strategy() { + @Override + public int hashCode(CommandParamData[][] o) { + return Arrays.deepHashCode(o); + } + + @Override + public boolean equals(CommandParamData[][] a, CommandParamData[][] b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + CommandParamData[] a1 = a[i]; + CommandParamData[] b1 = b[i]; + if (a1.length != b1.length) return false; + + for (int j = 0; j < a1.length; j++) { + if (!a1[j].equals(b1[j])) return false; + } + } + return true; + } + }; + + static { + List validColors = new ArrayList<>(NamedTextColor.NAMES.keys()); + validColors.add("reset"); + VALID_COLORS = validColors.toArray(new String[0]); + + List teamOptions = new ArrayList<>(Arrays.asList("list", "sidebar", "belowName")); + for (String color : NamedTextColor.NAMES.keys()) { + teamOptions.add("sidebar.team." + color); + } + VALID_SCOREBOARD_SLOTS = teamOptions.toArray(new String[0]); + } + @Override public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) { // Don't send command suggestions if they are disabled @@ -60,48 +108,50 @@ public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) return; } + CommandNode[] nodes = packet.getNodes(); List commandData = new ArrayList<>(); - Int2ObjectMap commands = new Int2ObjectOpenHashMap<>(); + IntSet commandNodes = new IntOpenHashSet(); + Set knownAliases = new HashSet<>(); + Map> commands = new Object2ObjectOpenCustomHashMap<>(PARAM_STRATEGY); Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); // Get the first node, it should be a root node - CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()]; + CommandNode rootNode = nodes[packet.getFirstNodeIndex()]; // Loop through the root nodes to get all commands for (int nodeIndex : rootNode.getChildIndices()) { - CommandNode node = packet.getNodes()[nodeIndex]; + CommandNode node = nodes[nodeIndex]; // Make sure we don't have duplicated commands (happens if there is more than 1 root node) - if (commands.containsKey(nodeIndex)) { continue; } - if (commands.containsValue(node.getName())) { continue; } + if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase())) continue; // Get and update the commandArgs list with the found arguments if (node.getChildIndices().length >= 1) { for (int childIndex : node.getChildIndices()) { - commandArgs.putIfAbsent(nodeIndex, new ArrayList<>()); - commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]); + commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]); } } - // Insert the command name into the list - commands.put(nodeIndex, node.getName()); + // Get and parse all params + CommandParamData[][] params = getParams(nodes[nodeIndex], nodes); + + // Insert the alias name into the command list + commands.computeIfAbsent(params, index -> new HashSet<>()).add(node.getName().toLowerCase()); } // The command flags, not sure what these do apart from break things List flags = Collections.emptyList(); // Loop through all the found commands - for (int commandID : commands.keySet()) { - String commandName = commands.get(commandID); - // Create a basic alias - CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", new String[] { commandName.toLowerCase() }, false); + for (Map.Entry> entry : commands.entrySet()) { + String commandName = entry.getValue().iterator().next(); // We know this has a value - // Get and parse all params - CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes()); + // Create a basic alias + CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", entry.getValue().toArray(new String[0]), false); // Build the completed command and add it to the final list - CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, params); + CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, entry.getKey()); commandData.add(data); } @@ -109,7 +159,7 @@ public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket(); availableCommandsPacket.getCommands().addAll(commandData); - GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); + session.getConnector().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); // Finally, send the commands to the client session.sendUpstreamPacket(availableCommandsPacket); @@ -119,11 +169,10 @@ public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) * Build the command parameter array for the given command * * @param commandNode The command to build the parameters for - * @param allNodes Every command node - * + * @param allNodes Every command node * @return An array of parameter option arrays */ - private CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) { + private static CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) { // Check if the command is an alias and redirect it if (commandNode.getRedirectIndex() != -1) { GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName()); @@ -136,16 +185,8 @@ private CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] al rootParam.buildChildren(allNodes); List treeData = rootParam.getTree(); - CommandParamData[][] params = new CommandParamData[treeData.size()][]; - - // Fill the nested params array - int i = 0; - for (CommandParamData[] tree : treeData) { - params[i] = tree; - i++; - } - return params; + return treeData.toArray(new CommandParamData[0][]); } return new CommandParamData[0][0]; @@ -155,14 +196,17 @@ private CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] al * Convert Java edition command types to Bedrock edition * * @param parser Command type to convert - * * @return Bedrock parameter data type */ - private CommandParamType mapCommandType(CommandParser parser) { - if (parser == null) { return CommandParamType.STRING; } + private static Object mapCommandType(CommandParser parser) { + if (parser == null) { + return CommandParamType.STRING; + } switch (parser) { case FLOAT: + case ROTATION: + case DOUBLE: return CommandParamType.FLOAT; case INTEGER: @@ -189,50 +233,44 @@ private CommandParamType mapCommandType(CommandParser parser) { return CommandParamType.JSON; case RESOURCE_LOCATION: + case FUNCTION: return CommandParamType.FILE_PATH; - case INT_RANGE: - return CommandParamType.INT_RANGE; - case BOOL: - case DOUBLE: - case STRING: - case VEC2: + return ENUM_BOOLEAN; + + case OPERATION: // ">=", "==", etc + return CommandParamType.OPERATOR; + case BLOCK_STATE: - case BLOCK_PREDICATE: + return BlockTranslator.getAllBlockIdentifiers(); + case ITEM_STACK: - case ITEM_PREDICATE: - case COLOR: - case COMPONENT: - case OBJECTIVE: - case OBJECTIVE_CRITERIA: - case OPERATION: // Possibly OPERATOR - case PARTICLE: - case ROTATION: - case SCOREBOARD_SLOT: - case SCORE_HOLDER: - case SWIZZLE: - case TEAM: - case ITEM_SLOT: - case MOB_EFFECT: - case FUNCTION: - case ENTITY_ANCHOR: - case RANGE: - case FLOAT_RANGE: + return ItemRegistry.ITEM_NAMES; + case ITEM_ENCHANTMENT: + return Enchantment.ALL_JAVA_IDENTIFIERS; //TODO: inventory branch use Java enums + case ENTITY_SUMMON: - case DIMENSION: - case TIME: + return EntityType.ALL_JAVA_IDENTIFIERS; + + case COLOR: + return VALID_COLORS; + + case SCOREBOARD_SLOT: + return VALID_SCOREBOARD_SLOTS; + default: return CommandParamType.STRING; } } @Getter - private class ParamInfo { - private CommandNode paramNode; - private CommandParamData paramData; - private List children; + @ToString + private static class ParamInfo { + private final CommandNode paramNode; + private final CommandParamData paramData; + private final List children; /** * Create a new parameter info object @@ -252,33 +290,50 @@ public ParamInfo(CommandNode paramNode, CommandParamData paramData) { * @param allNodes Every command node */ public void buildChildren(CommandNode[] allNodes) { - int enumIndex = -1; - for (int paramID : paramNode.getChildIndices()) { CommandNode paramNode = allNodes[paramID]; if (paramNode.getParser() == null) { - if (enumIndex == -1) { - enumIndex = children.size(); - - // Create the new enum command - CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[] { paramNode.getName() }, false); - children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, enumData, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); - } else { - // Get the existing enum - ParamInfo enumParamInfo = children.get(enumIndex); + boolean foundCompatible = false; + for (int i = 0; i < children.size(); i++) { + ParamInfo enumParamInfo = children.get(i); + // Check to make sure all descending nodes of this command are compatible - otherwise, create a new overload + if (isCompatible(allNodes, enumParamInfo.getParamNode(), paramNode)) { + foundCompatible = true; + // Extend the current list of enum values + String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1); + enumOptions[enumOptions.length - 1] = paramNode.getName(); + + // Re-create the command using the updated values + CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false); + children.set(i, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), this.paramNode.isExecutable(), enumData, null, null, Collections.emptyList()))); + break; + } + } - // Extend the current list of enum values - String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1); - enumOptions[enumOptions.length - 1] = paramNode.getName(); + if (!foundCompatible) { + // Create a new subcommand with this exact type + CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[]{paramNode.getName()}, false); - // Re-create the command using the updated values - CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false); - children.set(enumIndex, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), false, enumData, enumParamInfo.getParamData().getType(), null, Collections.emptyList()))); + // On setting optional: + // isExecutable is defined as a node "constitutes a valid command." + // Therefore, any children of the parameter must simply be optional. + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), this.paramNode.isExecutable(), enumData, null, null, Collections.emptyList()))); } - }else{ + } else { // Put the non-enum param into the list - children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, null, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); + Object mappedType = mapCommandType(paramNode.getParser()); + CommandEnumData enumData = null; + CommandParamType type = null; + if (mappedType instanceof String[]) { + enumData = new CommandEnumData(paramNode.getParser().name().toLowerCase(), (String[]) mappedType, false); + } else { + type = (CommandParamType) mappedType; + } + // IF enumData != null: + // In game, this will show up like + // So if paramNode.getName() == "value" and enumData.getName() == "bool": + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), this.paramNode.isExecutable(), enumData, type, null, Collections.emptyList()))); } } @@ -288,6 +343,64 @@ public void buildChildren(CommandNode[] allNodes) { } } + /** + * Comparing CommandNode type a and b, determine if they are in the same overload. + *

+ * Take the gamerule command, and let's present three "subcommands" you can perform: + * + *

    + *
  • gamerule doDaylightCycle true
  • + *
  • gamerule announceAdvancements false
  • + *
  • gamerule randomTickSpeed 3
  • + *
+ * + * While all three of them are indeed part of the same command, the command setting randomTickSpeed parses an int, + * while the others use boolean. In Bedrock, this should be presented as a separate overload to indicate that this + * does something a little different. + *

+ * Therefore, this function will return true if the first two are compared, as they use the same + * parsers. If the third is compared with either of the others, this function will return false. + *

+ * Here's an example of how the above would be presented to Bedrock (as of 1.16.200). Notice how the top two CommandParamData + * classes of each array are identical in type, but the following class is different: + *

+         *     overloads=[
+         *         [
+         *            CommandParamData(name=doDaylightCycle, optional=false, enumData=CommandEnumData(name=announceAdvancements, values=[announceAdvancements, doDaylightCycle], isSoft=false), type=STRING, postfix=null, options=[])
+         *            CommandParamData(name=value, optional=false, enumData=CommandEnumData(name=value, values=[true, false], isSoft=false), type=null, postfix=null, options=[])
+         *         ]
+         *         [
+         *            CommandParamData(name=randomTickSpeed, optional=false, enumData=CommandEnumData(name=randomTickSpeed, values=[randomTickSpeed], isSoft=false), type=STRING, postfix=null, options=[])
+         *            CommandParamData(name=value, optional=false, enumData=null, type=INT, postfix=null, options=[])
+         *         ]
+         *     ]
+         * 
+ * + * @return if these two can be merged into one overload. + */ + private boolean isCompatible(CommandNode[] allNodes, CommandNode a, CommandNode b) { + if (a == b) return true; + if (a.getParser() != b.getParser()) return false; + if (a.getChildIndices().length != b.getChildIndices().length) return false; + + for (int i = 0; i < a.getChildIndices().length; i++) { + boolean hasSimilarity = false; + CommandNode a1 = allNodes[a.getChildIndices()[i]]; + // Search "b" until we find a child that matches this one + for (int j = 0; j < b.getChildIndices().length; j++) { + if (isCompatible(allNodes, a1, allNodes[b.getChildIndices()[j]])) { + hasSimilarity = true; + break; + } + } + + if (!hasSimilarity) { + return false; + } + } + return true; + } + /** * Get the tree of every parameter node (recursive) * @@ -301,13 +414,10 @@ public List getTree() { List childTree = child.getTree(); // Un-pack the tree append the child node to it and push into the list - for (CommandParamData[] subchild : childTree) { - CommandParamData[] tmpTree = new ArrayList() { - { - add(child.getParamData()); - addAll(Arrays.asList(subchild)); - } - }.toArray(new CommandParamData[0]); + for (CommandParamData[] subChild : childTree) { + CommandParamData[] tmpTree = new CommandParamData[subChild.length + 1]; + tmpTree[0] = child.getParamData(); + System.arraycopy(subChild, 0, tmpTree, 1, subChild.length); treeParamData.add(tmpTree); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index b047999e7d1..06d7247282d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -386,4 +386,11 @@ public static String getPickItem(int javaId) { } return itemIdentifier; } + + /** + * @return a list of all Java block identifiers. For use with command suggestions. + */ + public static String[] getAllBlockIdentifiers() { + return JAVA_ID_TO_JAVA_IDENTIFIER_MAP.values().toArray(new String[0]); + } } From 9208943ac643bea69d1e85dd6b535ce3ea70b8c9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 17 Feb 2021 18:00:53 -0500 Subject: [PATCH 5/6] Bump Network and Netty dependencies (#1775) --- connector/pom.xml | 57 +++++++++++++++++-- .../network/session/GeyserSession.java | 9 ++- .../session/auth/BedrockClientData.java | 2 + .../player/BedrockActionTranslator.java | 3 +- .../geysermc/connector/skin/SkinManager.java | 2 +- .../connector/skin/SkullSkinManager.java | 2 +- 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 77da3e4f21b..8b1bfe510cb 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -10,6 +10,10 @@ connector + + 4.1.59.Final + + org.geysermc @@ -26,14 +30,13 @@ com.github.CloudburstMC.Protocol bedrock-v422 - d41b84e86c + 294e7e5 compile net.sf.trove4j trove - com.nukkitx.network raknet @@ -41,10 +44,16 @@ - com.nukkitx.network + com.github.CloudburstMC.Network raknet - 1.6.20 + a94d2dd compile + + + io.netty + * + + com.nukkitx.fastutil @@ -147,15 +156,51 @@ io.netty netty-resolver-dns - 4.1.43.Final + ${netty.version} compile + + io.netty + netty-resolver-dns-native-macos + ${netty.version} + compile + osx-x86_64 + io.netty netty-codec-haproxy - 4.1.56.Final + ${netty.version} + compile + + + + io.netty + netty-handler + ${netty.version} + compile + + + io.netty + netty-transport-native-epoll + ${netty.version} + compile + linux-x86_64 + + + io.netty + netty-transport-native-epoll + ${netty.version} + compile + linux-aarch_64 + + + io.netty + netty-transport-native-kqueue + ${netty.version} compile + osx-x86_64 + org.reflections reflections diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 2f15cab754b..999a2a50afc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -839,7 +839,14 @@ private void startGame() { startGamePacket.setMultiplayerCorrelationId(""); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); - startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); + startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); // can be removed once 1.16.200 support is dropped + + SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); + settings.setMovementMode(AuthoritativeMovementMode.CLIENT); + settings.setRewindHistorySize(0); + settings.setServerAuthoritativeBlockBreaking(false); + startGamePacket.setPlayerMovementSettings(settings); + upstream.sendPacket(startGamePacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 10075a9a479..16e06c06644 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -103,6 +103,8 @@ public class BedrockClientData { private String skinColor; @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; + @JsonProperty(value = "PlayFabId") + private String playFabId; public enum UIProfile { @JsonEnumDefaultValue diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index 789df85c2d2..c248b57a543 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -37,6 +37,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.PlayerActionType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; @@ -64,7 +65,7 @@ public void translate(PlayerActionPacket packet, GeyserSession session) { return; // Send book update before any player action - if (packet.getAction() != PlayerActionPacket.Action.RESPAWN) { + if (packet.getAction() != PlayerActionType.RESPAWN) { session.getBookEditCache().checkForSend(); } diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java index fb8336aca07..5a0e41ed560 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java @@ -81,7 +81,7 @@ public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, U String capeId, byte[] capeData, SkinProvider.SkinGeometry geometry) { SerializedSkin serializedSkin = SerializedSkin.of( - skinId, geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), + skinId, "", geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), ImageData.of(capeData), geometry.getGeometryData(), "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId ); diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java index 562e2c50f6c..7481b70bcdd 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java @@ -42,7 +42,7 @@ public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinD // Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png skinId = skinId + "_skull"; return SerializedSkin.of( - skinId, SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), + skinId, "", SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(), "", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId ); From c4573bb73d5b8bb749904a48e3d9ae2332a62bca Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Thu, 18 Feb 2021 01:25:41 +0200 Subject: [PATCH 6/6] HAProxy PROXY protocol support for upstream connections (#1713) * Ignore unknown properties on configuration subclasses * Implement upstream PROXY protocol support --- .../geysermc/connector/GeyserConnector.java | 9 +- .../configuration/GeyserConfiguration.java | 11 +++ .../GeyserJacksonConfiguration.java | 31 ++++++ .../connector/network/CIDRMatcher.java | 95 +++++++++++++++++++ .../network/ConnectorServerEventHandler.java | 16 ++++ .../network/session/GeyserSession.java | 6 +- .../network/session/UpstreamSession.java | 2 +- connector/src/main/resources/config.yml | 8 ++ 8 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3494f8c20e3..0b7f8464628 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.nukkitx.network.raknet.RakNetConstants; +import com.nukkitx.network.util.EventLoops; import com.nukkitx.protocol.bedrock.BedrockServer; import lombok.Getter; import lombok.Setter; @@ -196,7 +197,13 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); logger.debug("Setting MTU to " + config.getMtu()); - bedrockServer = new BedrockServer(new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort())); + boolean enableProxyProtocol = config.getBedrock().isEnableProxyProtocol(); + bedrockServer = new BedrockServer( + new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()), + 1, + EventLoops.commonGroup(), + enableProxyProtocol + ); bedrockServer.setHandler(new ConnectorServerEventHandler(this)); bedrockServer.bind().whenComplete((avoid, throwable) -> { if (throwable == null) { diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 31bcbe995c1..6052bd28328 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -27,9 +27,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.network.CIDRMatcher; import org.geysermc.connector.utils.LanguageUtils; import java.nio.file.Path; +import java.util.List; import java.util.Map; public interface GeyserConfiguration { @@ -106,6 +108,15 @@ interface IBedrockConfiguration { String getMotd2(); String getServerName(); + + boolean isEnableProxyProtocol(); + + List getProxyProtocolWhitelistedIPs(); + + /** + * @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()} + */ + List getWhitelistedIPsMatchers(); } interface IRemoteConfiguration { diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 4e03da52f0a..70aa3ff5ddc 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -25,16 +25,21 @@ package org.geysermc.connector.configuration; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.serializer.AsteriskSerializer; +import org.geysermc.connector.network.CIDRMatcher; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; @Getter @JsonIgnoreProperties(ignoreUnknown = true) @@ -122,6 +127,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private MetricsInfo metrics = new MetricsInfo(); @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { @AsteriskSerializer.Asterisk(sensitive = true) private String address = "0.0.0.0"; @@ -137,9 +143,33 @@ public static class BedrockConfiguration implements IBedrockConfiguration { @JsonProperty("server-name") private String serverName = GeyserConnector.NAME; + + @JsonProperty("enable-proxy-protocol") + private boolean enableProxyProtocol = false; + + @JsonProperty("proxy-protocol-whitelisted-ips") + private List proxyProtocolWhitelistedIPs = Collections.emptyList(); + + @JsonIgnore + private List whitelistedIPsMatchers = null; + + @Override + public List getWhitelistedIPsMatchers() { + // Effective Java, Third Edition; Item 83: Use lazy initialization judiciously + List matchers = this.whitelistedIPsMatchers; + if (matchers == null) { + synchronized (this) { + this.whitelistedIPsMatchers = matchers = proxyProtocolWhitelistedIPs.stream() + .map(CIDRMatcher::new) + .collect(Collectors.toList()); + } + } + return Collections.unmodifiableList(matchers); + } } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class RemoteConfiguration implements IRemoteConfiguration { @Setter @AsteriskSerializer.Asterisk(sensitive = true) @@ -173,6 +203,7 @@ public static class UserAuthenticationInfo implements IUserAuthenticationInfo { } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class MetricsInfo implements IMetricsInfo { private boolean enabled = true; diff --git a/connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java b/connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java new file mode 100644 index 00000000000..57e58ecc297 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/* + * Taken & modified from TCPShield, licensed under MIT. See https://github.com/TCPShield/RealIP/blob/master/LICENSE + * + * https://github.com/TCPShield/RealIP/blob/32d422a9523cb6e25b571072851f3306bb8bbc4f/src/main/java/net/tcpshield/tcpshield/validation/cidr/CIDRMatcher.java + */ +public class CIDRMatcher { + private final int maskBits; + private final int maskBytes; + private final boolean simpleCIDR; + private final InetAddress cidrAddress; + + public CIDRMatcher(String ipAddress) { + String[] split = ipAddress.split("/", 2); + + String parsedIPAddress; + if (split.length == 2) { + parsedIPAddress = split[0]; + + this.maskBits = Integer.parseInt(split[1]); + this.simpleCIDR = maskBits == 32; + } else { + parsedIPAddress = ipAddress; + + this.maskBits = -1; + this.simpleCIDR = true; + } + + this.maskBytes = simpleCIDR ? -1 : maskBits / 8; + + try { + cidrAddress = InetAddress.getByName(parsedIPAddress); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + public boolean matches(InetAddress inetAddress) { + // check if IP is IPv4 or IPv6 + if (cidrAddress.getClass() != inetAddress.getClass()) { + return false; + } + + // check for equality if it's a simple CIDR + if (simpleCIDR) { + return inetAddress.equals(cidrAddress); + } + + byte[] inetAddressBytes = inetAddress.getAddress(); + byte[] requiredAddressBytes = cidrAddress.getAddress(); + + byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07)); + + for (int i = 0; i < maskBytes; i++) { + if (inetAddressBytes[i] != requiredAddressBytes[i]) { + return false; + } + } + + if (finalByte != 0) { + return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte); + } + + return true; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index a1ebbc8d0ab..bd5030a8b4d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -40,6 +40,7 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.util.List; public class ConnectorServerEventHandler implements BedrockServerEventHandler { /* @@ -60,6 +61,21 @@ public ConnectorServerEventHandler(GeyserConnector connector) { @Override public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { + List allowedProxyIPs = connector.getConfig().getBedrock().getProxyProtocolWhitelistedIPs(); + if (connector.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) { + boolean isWhitelistedIP = false; + for (CIDRMatcher matcher : connector.getConfig().getBedrock().getWhitelistedIPsMatchers()) { + if (matcher.matches(inetSocketAddress.getAddress())) { + isWhitelistedIP = true; + break; + } + } + + if (!isWhitelistedIP) { + return false; + } + } + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress)); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 999a2a50afc..ac872d909e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -93,6 +93,7 @@ import org.geysermc.floodgate.util.EncryptionUtil; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -378,7 +379,8 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes())); bedrockServerSession.addDisconnectHandler(disconnectReason -> { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); + InetAddress address = bedrockServerSession.getRealAddress().getAddress(); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason)); disconnect(disconnectReason.name()); connector.removePlayer(this); @@ -588,7 +590,7 @@ public void packetSending(PacketSendingEvent event) { clientData.getDeviceOS().ordinal(), clientData.getLanguageCode(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() + upstream.getAddress().getAddress().getHostAddress() )); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java index 04e208af3fc..f973574b0ce 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java @@ -61,6 +61,6 @@ public boolean isClosed() { } public InetSocketAddress getAddress() { - return session.getAddress(); + return session.getRealAddress(); } } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index da46f5804ba..ce202a3c1f1 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -23,6 +23,14 @@ bedrock: motd2: "Another Geyser server." # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. server-name: "Geyser" + # Whether to enable PROXY protocol or not for clients. You DO NOT WANT this feature unless you run UDP reverse proxy + # in front of your Geyser instance. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # Both IP addresses and subnets are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16" ] remote: # The IP address of the remote (Java Edition) server # If it is "auto", for standalone version the remote address will be set to 127.0.0.1,