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;
}
@@ -71,16 +96,16 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
BedrockPong pong = new BedrockPong();
pong.setEdition("MCPE");
- pong.setGameType("Default");
+ pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59
pong.setNintendoLimited(false);
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
- pong.setVersion(null); // Server tries to connect either way and it looks better
+ pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
pong.setIpv4Port(config.getBedrock().getPort());
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
String mainMotd = motd[0]; // First line of the motd.
- String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank.
+ String subMotd = (motd.length != 1) ? motd[1] : GeyserConnector.NAME; // Second line of the motd if present, otherwise default.
pong.setMotd(mainMotd.trim());
pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
@@ -97,15 +122,28 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
pong.setMaximumPlayerCount(config.getMaxPlayers());
}
+ // Fallbacks to prevent errors and allow Bedrock to see the server
+ if (pong.getMotd() == null || pong.getMotd().trim().isEmpty()) {
+ pong.setMotd(GeyserConnector.NAME);
+ }
+ if (pong.getSubMotd() == null || pong.getSubMotd().trim().isEmpty()) {
+ // Sub-MOTD cannot be empty as of 1.16.210.59
+ pong.setSubMotd(GeyserConnector.NAME);
+ }
+
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
// We don't know why, though
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
- if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
- // Remove the sub-MOTD first since that only appears locally
- pong.setSubMotd("");
- if (motdArray.length > 338) {
+ int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length;
+ if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) {
+ // Shorten the sub-MOTD first since that only appears locally
+ if (subMotdLength > BRAND_BYTES_LENGTH) {
+ pong.setSubMotd(GeyserConnector.NAME);
+ subMotdLength = BRAND_BYTES_LENGTH;
+ }
+ if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) {
// If the top MOTD is still too long, we chop it down
- byte[] newMotdArray = new byte[339];
+ byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength];
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
}
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/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
index 9c2c8de6e7d..d86be8b8887 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
@@ -102,8 +102,7 @@
import org.geysermc.floodgate.util.EncryptionUtil;
import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
@@ -182,6 +181,9 @@ public class GeyserSession implements CommandSender {
@Setter
private boolean sprinting;
+ /**
+ * Not updated if cache chunks is enabled.
+ */
@Setter
private boolean jumping;
@@ -391,8 +393,8 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
EventManager.getInstance().triggerEvent(new SessionDisconnectEvent(this, 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,8 +590,10 @@ private void connectDownstream() {
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
}
- // Let Geyser handle sending the keep alive
- downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
+ if (connector.getConfig().isForwardPlayerPing()) {
+ // Let Geyser handle sending the keep alive
+ downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
+ }
downstream.getSession().addListener(new SessionAdapter() {
@Override
public void packetSending(PacketSendingEvent event) {
@@ -604,7 +608,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);
@@ -869,7 +873,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);
+
sendUpstreamPacket(startGamePacket);
}
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/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/EntityIdentifierRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java
index f4c0f9abc13..fb6d5b93dec 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java
@@ -38,7 +38,7 @@
*/
public class EntityIdentifierRegistry {
- public static NbtMap ENTITY_IDENTIFIERS;
+ public static final NbtMap ENTITY_IDENTIFIERS;
private EntityIdentifierRegistry() {
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java
index f48331b76ef..6f76326eeef 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java
@@ -30,6 +30,7 @@
import com.github.steveice10.packetlib.packet.Packet;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import org.geysermc.common.PlatformType;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.event.EventManager;
@@ -96,12 +97,15 @@ public static void init() {
public boolean translate(Class extends P> clazz, P packet, GeyserSession session) {
if (!session.getUpstream().isClosed() && !session.isClosed()) {
try {
- if (translators.containsKey(clazz)) {
- ((PacketTranslator
) translators.get(clazz)).translate(packet, session);
+ PacketTranslator
translator = (PacketTranslator
) translators.get(clazz);
+ if (translator != null) {
+ translator.translate(packet, session);
return true;
} else {
- if (!IGNORED_PACKETS.contains(clazz))
+ if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) {
+ // Other debug logs already take care of Bedrock packets for us if on standalone
GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
+ }
}
} catch (Throwable ex) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex);
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java
index 38e5981e608..56387fd5816 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java
@@ -33,23 +33,25 @@
import org.geysermc.floodgate.util.DeviceOS;
/**
- * Used to send the keep alive packet back to the server
+ * Used to send the forwarded keep alive packet back to the server
*/
@Translator(packet = NetworkStackLatencyPacket.class)
public class BedrockNetworkStackLatencyTranslator extends PacketTranslator {
@Override
public void translate(NetworkStackLatencyPacket packet, GeyserSession session) {
- long pingId;
- // so apparently, as of 1.16.200
- // PS4 divides the network stack latency timestamp FOR US!!!
- // WTF
- if (session.getClientData().getDeviceOS().equals(DeviceOS.NX)) {
- // Ignore the weird DeviceOS, our order is wrong and will be fixed in Floodgate 2.0
- pingId = packet.getTimestamp();
- } else {
- pingId = packet.getTimestamp() / 1000;
+ if (session.getConnector().getConfig().isForwardPlayerPing()) {
+ long pingId;
+ // so apparently, as of 1.16.200
+ // PS4 divides the network stack latency timestamp FOR US!!!
+ // WTF
+ if (session.getClientData().getDeviceOS().equals(DeviceOS.NX)) {
+ // Ignore the weird DeviceOS, our order is wrong and will be fixed in Floodgate 2.0
+ pingId = packet.getTimestamp();
+ } else {
+ pingId = packet.getTimestamp() / 1000;
+ }
+ session.sendDownstreamPacket(new ClientKeepAlivePacket(pingId));
}
- session.sendDownstreamPacket(new ClientKeepAlivePacket(pingId));
}
}
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 c4dbbec405e..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();
}
@@ -205,10 +206,11 @@ public void translate(PlayerActionPacket packet, GeyserSession session) {
session.getEntityCache().updateBossBars();
break;
case JUMP:
- session.setJumping(true);
- session.getConnector().getGeneralThreadPool().schedule(() -> {
- session.setJumping(false);
- }, 1, TimeUnit.SECONDS);
+ if (!session.getConnector().getConfig().isCacheChunks()) {
+ // Save the jumping status for determining teleport status
+ session.setJumping(true);
+ session.getConnector().getGeneralThreadPool().schedule(() -> session.setJumping(false), 1, TimeUnit.SECONDS);
+ }
break;
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java
index 203e4406f3a..77392a99ae1 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java
@@ -177,24 +177,24 @@ public void recalculatePosition() {
session.sendUpstreamPacket(movePlayerPacket);
}
- public List getPlayerCollidableBlocks() {
+ public List getCollidableBlocks(BoundingBox box) {
List blocks = new ArrayList<>();
- Vector3d position = Vector3d.from(playerBoundingBox.getMiddleX(),
- playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
- playerBoundingBox.getMiddleZ());
+ Vector3d position = Vector3d.from(box.getMiddleX(),
+ box.getMiddleY() - (box.getSizeY() / 2),
+ box.getMiddleZ());
- // Loop through all blocks that could collide with the player
- int minCollisionX = (int) Math.floor(position.getX() - ((playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE));
- int maxCollisionX = (int) Math.floor(position.getX() + (playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE);
+ // Loop through all blocks that could collide
+ int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE));
+ int maxCollisionX = (int) Math.floor(position.getX() + (box.getSizeX() / 2) + COLLISION_TOLERANCE);
// Y extends 0.5 blocks down because of fence hitboxes
int minCollisionY = (int) Math.floor(position.getY() - 0.5);
- int maxCollisionY = (int) Math.floor(position.getY() + playerBoundingBox.getSizeY());
+ int maxCollisionY = (int) Math.floor(position.getY() + box.getSizeY());
- int minCollisionZ = (int) Math.floor(position.getZ() - ((playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE));
- int maxCollisionZ = (int) Math.floor(position.getZ() + (playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE);
+ int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE));
+ int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE);
for (int y = minCollisionY; y < maxCollisionY + 1; y++) {
for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
@@ -207,6 +207,10 @@ public List getPlayerCollidableBlocks() {
return blocks;
}
+ public List getPlayerCollidableBlocks() {
+ return getCollidableBlocks(playerBoundingBox);
+ }
+
/**
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
* cancelled
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/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
index f25ccab82a1..64d8d7730cc 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
@@ -309,9 +309,8 @@ public CompoundTag translateToJavaNBT(String name, NbtMap tag) {
CompoundTag javaTag = new CompoundTag(name);
Map javaValue = javaTag.getValue();
if (tag != null && !tag.isEmpty()) {
- for (String str : tag.keySet()) {
- Object bedrockTag = tag.get(str);
- Tag translatedTag = translateToJavaNBT(str, bedrockTag);
+ for (Map.Entry entry : tag.entrySet()) {
+ Tag translatedTag = translateToJavaNBT(entry.getKey(), entry.getValue());
if (translatedTag == null)
continue;
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/java/JavaKeepAliveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java
index 76e9b0958f0..8506389f3fb 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java
@@ -39,6 +39,9 @@ public class JavaKeepAliveTranslator extends PacketTranslator iterator = session.getSkullCache().keySet().iterator();
while (iterator.hasNext()) {
Vector3i position = iterator.next();
- if (Math.floor(position.getX() / 16) == packet.getX() && Math.floor(position.getZ() / 16) == packet.getZ()) {
+ if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) {
session.getSkullCache().get(position).despawnEntity(session);
iterator.remove();
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
index 4ffb96d0013..f85ac293c3c 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
@@ -49,70 +49,72 @@ public class BlockStateValues {
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap();
+ private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
/**
* Determines if the block state contains Bedrock block information
*
- * @param entry The String to JsonNode map used in BlockTranslator
+ * @param javaId The Java Identifier of the block
* @param javaBlockState the Java Block State of the block
+ * @param blockData JsonNode of info about the block from blocks.json
*/
- public static void storeBlockStateValues(Map.Entry entry, int javaBlockState) {
- JsonNode bannerColor = entry.getValue().get("banner_color");
+ public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) {
+ JsonNode bannerColor = blockData.get("banner_color");
if (bannerColor != null) {
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
return; // There will never be a banner color and a skull variant
}
- JsonNode bedColor = entry.getValue().get("bed_color");
+ JsonNode bedColor = blockData.get("bed_color");
if (bedColor != null) {
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
return;
}
- if (entry.getKey().contains("command_block")) {
- COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0);
+ if (javaId.contains("command_block")) {
+ COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
return;
}
- if (entry.getValue().get("double_chest_position") != null) {
- boolean isX = (entry.getValue().get("x") != null);
- boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) ||
- (entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean()));
- boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left"));
+ if (blockData.get("double_chest_position") != null) {
+ boolean isX = (blockData.get("x") != null);
+ boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) ||
+ (blockData.get("z") != null && blockData.get("z").asBoolean()));
+ boolean isLeft = (blockData.get("double_chest_position").asText().contains("left"));
DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft));
return;
}
- if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) {
- FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", ""));
+ if (javaId.contains("potted_") || javaId.contains("flower_pot")) {
+ FLOWER_POT_VALUES.put(javaBlockState, javaId.replace("potted_", ""));
return;
}
- JsonNode notePitch = entry.getValue().get("note_pitch");
+ JsonNode notePitch = blockData.get("note_pitch");
if (notePitch != null) {
- NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue());
+ NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue());
return;
}
- if (entry.getKey().contains("piston")) {
+ if (javaId.contains("piston")) {
// True if extended, false if not
- PISTON_VALUES.put(javaBlockState, entry.getKey().contains("extended=true"));
- IS_STICKY_PISTON.put(javaBlockState, entry.getKey().contains("sticky"));
+ PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true"));
+ IS_STICKY_PISTON.put(javaBlockState, javaId.contains("sticky"));
return;
}
- JsonNode skullVariation = entry.getValue().get("variation");
+ JsonNode skullVariation = blockData.get("variation");
if (skullVariation != null) {
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
}
- JsonNode skullRotation = entry.getValue().get("skull_rotation");
+ JsonNode skullRotation = blockData.get("skull_rotation");
if (skullRotation != null) {
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
}
- if (entry.getKey().contains("wall_skull") || entry.getKey().contains("wall_head")) {
- String direction = entry.getKey().substring(entry.getKey().lastIndexOf("facing=") + 7);
+ if (javaId.contains("wall_skull") || javaId.contains("wall_head")) {
+ String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7);
int rotation = 0;
switch (direction.substring(0, direction.length() - 1)) {
case "north":
@@ -131,10 +133,16 @@ public static void storeBlockStateValues(Map.Entry entry, int
SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation);
}
- JsonNode shulkerDirection = entry.getValue().get("shulker_direction");
+ JsonNode shulkerDirection = blockData.get("shulker_direction");
if (shulkerDirection != null) {
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
}
+
+ if (javaId.startsWith("minecraft:water")) {
+ String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
+ int level = Integer.parseInt(strLevel);
+ WATER_LEVEL.put(javaBlockState, level);
+ }
}
/**
@@ -263,4 +271,15 @@ public static Int2IntMap getSkullWallDirections() {
public static byte getShulkerBoxDirection(int state) {
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
}
+
+ /**
+ * Get the level of water from the block state.
+ * This is used in FishingHookEntity to create splash sounds when the hook hits the water.
+ *
+ * @param state BlockState of the block
+ * @return The water level or -1 if the block isn't water
+ */
+ public static int getWaterLevel(int state) {
+ return WATER_LEVEL.getOrDefault(state, -1);
+ }
}
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 d0a56dc6385..0fde056d9ce 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
@@ -182,7 +182,7 @@ public class BlockTranslator {
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
- BlockStateValues.storeBlockStateValues(entry, javaRuntimeId);
+ BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue());
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
@@ -383,4 +383,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]);
+ }
}
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 30c68c875f8..135bd57077f 100644
--- a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java
+++ b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java
@@ -33,6 +33,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.event.EventManager;
import org.geysermc.connector.event.events.geyser.LoadBedrockSkinEvent;
@@ -82,7 +83,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
);
@@ -165,7 +166,7 @@ public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSessio
geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());
// Store the skin and geometry for the ears
- SkinProvider.storeEarSkin(entity.getUuid(), skin);
+ SkinProvider.storeEarSkin(skin);
SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
}
}
@@ -273,7 +274,10 @@ public static GameProfileData from(GameProfile profile) {
return new GameProfileData(skinUrl, capeUrl, isAlex);
} catch (Exception exception) {
- GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName() + ": " + exception.getMessage());
+ GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName());
+ if (GeyserConnector.getInstance().getConfig().isDebugMode()) {
+ exception.printStackTrace();
+ }
return loadBedrockOrOfflineSkin(profile);
}
}
@@ -288,7 +292,7 @@ private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) {
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl();
- if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
+ if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserConnector.getInstance().getAuthType() != AuthType.ONLINE) {
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
if (session != null) {
diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java
index 3f236932a30..c4d4bc486af 100644
--- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java
+++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java
@@ -79,13 +79,12 @@ public class SkinProvider {
.build();
private static final Map> requestedCapes = new ConcurrentHashMap<>();
- public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false);
private static final Map cachedGeometry = new ConcurrentHashMap<>();
public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
- public static String EARS_GEOMETRY;
- public static String EARS_GEOMETRY_SLIM;
- public static SkinGeometry SKULL_GEOMETRY;
+ public static final String EARS_GEOMETRY;
+ public static final String EARS_GEOMETRY_SLIM;
+ public static final SkinGeometry SKULL_GEOMETRY;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@@ -229,15 +228,15 @@ public static CompletableFuture requestUnofficialCape(Cape officialCape, U
return CompletableFuture.completedFuture(officialCape);
}
- public static CompletableFuture requestEars(String earsUrl, EarsProvider provider, boolean newThread, Skin skin) {
+ public static CompletableFuture requestEars(String earsUrl, boolean newThread, Skin skin) {
if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin);
CompletableFuture future;
if (newThread) {
- future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE)
+ future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), EXECUTOR_SERVICE)
.whenCompleteAsync((outSkin, throwable) -> { });
} else {
- Skin ears = supplyEars(skin, earsUrl, provider); // blocking
+ Skin ears = supplyEars(skin, earsUrl); // blocking
future = CompletableFuture.completedFuture(ears);
}
return future;
@@ -255,7 +254,7 @@ public static CompletableFuture requestEars(String earsUrl, EarsProvider p
public static CompletableFuture requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) {
for (EarsProvider provider : EarsProvider.VALUES) {
Skin skin1 = getOrDefault(
- requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin),
+ requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin),
officialSkin, 4
);
if (skin1.isEars()) {
@@ -295,12 +294,11 @@ public static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte
}
/**
- * Stores the ajusted skin with the ear texture to the cache
+ * Stores the adjusted skin with the ear texture to the cache
*
- * @param playerID The UUID to cache it against
* @param skin The skin to cache
*/
- public static void storeEarSkin(UUID playerID, Skin skin) {
+ public static void storeEarSkin(Skin skin) {
cachedSkins.put(skin.getTextureUrl(), skin);
}
@@ -324,7 +322,7 @@ private static Skin supplySkin(UUID uuid, String textureUrl) {
}
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
- byte[] cape = new byte[0];
+ byte[] cape = EMPTY_CAPE.getCapeData();
try {
cape = requestImage(capeUrl, provider);
} catch (Exception ignored) {} // just ignore I guess
@@ -334,7 +332,7 @@ private static Cape supplyCape(String capeUrl, CapeProvider provider) {
return new Cape(
capeUrl,
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
- cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(),
+ cape,
System.currentTimeMillis(),
cape.length == 0
);
@@ -345,10 +343,9 @@ private static Cape supplyCape(String capeUrl, CapeProvider provider) {
*
* @param existingSkin The players current skin
* @param earsUrl The URL to get the ears texture from
- * @param provider The ears texture provider
* @return The updated skin with ears
*/
- private static Skin supplyEars(Skin existingSkin, String earsUrl, EarsProvider provider) {
+ private static Skin supplyEars(Skin existingSkin, String earsUrl) {
try {
// Get the ears texture
BufferedImage ears = ImageIO.read(new URL(earsUrl));
@@ -415,14 +412,15 @@ private static byte[] requestImage(String imageUrl, CapeProvider provider) throw
// if the requested image is a cape
if (provider != null) {
- while(image.getWidth() > 64) {
- image = scale(image);
+ if (image.getWidth() > 64) {
+ image = scale(image, 64, 32);
+ }
+ } else {
+ // Very rarely, skins can be larger than Minecraft's default.
+ // Bedrock will not render anything above a width of 128.
+ if (image.getWidth() > 128) {
+ image = scale(image, 128, image.getHeight() / (image.getWidth() / 128));
}
- BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB);
- Graphics g = newImage.createGraphics();
- g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
- g.dispose();
- image = newImage;
}
byte[] data = bufferedImageToImageData(image);
@@ -506,12 +504,13 @@ private static BufferedImage readFiveZigCape(String url) throws IOException {
return null;
}
- private static BufferedImage scale(BufferedImage bufferedImage) {
- BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB);
+ private static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) {
+ BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = resized.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- g2.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, null);
+ g2.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
g2.dispose();
+ bufferedImage.flush();
return resized;
}
@@ -579,17 +578,17 @@ public static T getOrDefault(CompletableFuture future, T defaultValue, in
@AllArgsConstructor
@Getter
public static class SkinAndCape {
- private Skin skin;
- private Cape cape;
+ private final Skin skin;
+ private final Cape cape;
}
@AllArgsConstructor
@Getter
public static class Skin {
private UUID skinOwner;
- private String textureUrl;
- private byte[] skinData;
- private long requestedOn;
+ private final String textureUrl;
+ private final byte[] skinData;
+ private final long requestedOn;
private boolean updated;
private boolean ears;
@@ -603,19 +602,19 @@ private Skin(long requestedOn, String textureUrl, byte[] skinData) {
@AllArgsConstructor
@Getter
public static class Cape {
- private String textureUrl;
- private String capeId;
- private byte[] capeData;
- private long requestedOn;
- private boolean failed;
+ private final String textureUrl;
+ private final String capeId;
+ private final byte[] capeData;
+ private final long requestedOn;
+ private final boolean failed;
}
@AllArgsConstructor
@Getter
public static class SkinGeometry {
- private String geometryName;
- private String geometryData;
- private boolean failed;
+ private final String geometryName;
+ private final String geometryData;
+ private final boolean failed;
/**
* Generate generic geometry
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 644323a427d..7481b70bcdd 100644
--- a/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java
+++ b/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java
@@ -27,65 +27,42 @@
import com.nukkitx.protocol.bedrock.data.skin.ImageData;
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
-import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
+import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.Collections;
-import java.util.UUID;
import java.util.function.Consumer;
public class SkullSkinManager extends SkinManager {
- public static PlayerListPacket.Entry buildSkullEntryManually(UUID uuid, String username, long geyserId,
- String skinId, byte[] skinData) {
+ public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) {
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
skinId = skinId + "_skull";
- SerializedSkin serializedSkin = SerializedSkin.of(
- skinId, SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
+ return SerializedSkin.of(
+ 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
);
-
- PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid);
- entry.setName(username);
- entry.setEntityId(geyserId);
- entry.setSkin(serializedSkin);
- entry.setXuid("");
- entry.setPlatformChatId("");
- entry.setTeacher(false);
- entry.setTrustedSkin(true);
- return entry;
}
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session,
Consumer skinConsumer) {
GameProfileData data = GameProfileData.from(entity.getProfile());
- SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), false)
+ SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), true)
.whenCompleteAsync((skin, throwable) -> {
try {
if (session.getUpstream().isInitialized()) {
- PlayerListPacket.Entry updatedEntry = buildSkullEntryManually(
- entity.getUuid(),
- entity.getUsername(),
- entity.getGeyserId(),
- skin.getTextureUrl(),
- skin.getSkinData()
- );
-
- PlayerListPacket playerAddPacket = new PlayerListPacket();
- playerAddPacket.setAction(PlayerListPacket.Action.ADD);
- playerAddPacket.getEntries().add(updatedEntry);
- session.sendUpstreamPacket(playerAddPacket);
-
- // It's a skull. We don't want them in the player list.
- PlayerListPacket playerRemovePacket = new PlayerListPacket();
- playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
- playerRemovePacket.getEntries().add(updatedEntry);
- session.sendUpstreamPacket(playerRemovePacket);
+ PlayerSkinPacket packet = new PlayerSkinPacket();
+ packet.setUuid(entity.getUuid());
+ packet.setOldSkinName("");
+ packet.setNewSkinName(skin.getTextureUrl());
+ packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
+ packet.setTrustedSkin(true);
+ session.sendUpstreamPacket(packet);
}
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
diff --git a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java
index 5a49fd9beed..816b718aa05 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java
@@ -26,7 +26,6 @@
package org.geysermc.connector.utils;
import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
-import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.concurrent.TimeUnit;
@@ -36,11 +35,10 @@
* Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind
*/
public class CooldownUtils {
+ private static boolean SHOW_COOLDOWN;
- private final static boolean SHOW_COOLDOWN;
-
- static {
- SHOW_COOLDOWN = GeyserConnector.getInstance().getConfig().isShowCooldown();
+ public static void setShowCooldown(boolean showCooldown) {
+ SHOW_COOLDOWN = showCooldown;
}
/**
@@ -116,5 +114,4 @@ private static String getTitle(GeyserSession session) {
}
return builder.toString();
}
-
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
index 8349d7c8771..1715eb4eb00 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
@@ -92,21 +92,22 @@ public static File fileOrCopiedFromResource(String name, Function format) throws IOException {
if (!file.exists()) {
+ //noinspection ResultOfMethodCallIgnored
file.createNewFile();
- FileOutputStream fos = new FileOutputStream(file);
- InputStream input = GeyserConnector.class.getResourceAsStream("/" + name); // resources need leading "/" prefix
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ try (InputStream input = GeyserConnector.class.getResourceAsStream("/" + name)) { // resources need leading "/" prefix
+ byte[] bytes = new byte[input.available()];
- byte[] bytes = new byte[input.available()];
+ //noinspection ResultOfMethodCallIgnored
+ input.read(bytes);
- input.read(bytes);
+ for(char c : format.apply(new String(bytes)).toCharArray()) {
+ fos.write(c);
+ }
- for(char c : format.apply(new String(bytes)).toCharArray()) {
- fos.write(c);
+ fos.flush();
+ }
}
-
- fos.flush();
- input.close();
- fos.close();
}
return file;
@@ -124,14 +125,13 @@ public static void writeFile(File file, char[] data) throws IOException {
file.createNewFile();
}
- FileOutputStream fos = new FileOutputStream(file);
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ for (char c : data) {
+ fos.write(c);
+ }
- for (char c : data) {
- fos.write(c);
+ fos.flush();
}
-
- fos.flush();
- fos.close();
}
/**
@@ -237,9 +237,10 @@ public static byte[] readAllBytes(InputStream stream) {
try {
int size = stream.available();
byte[] bytes = new byte[size];
- BufferedInputStream buf = new BufferedInputStream(stream);
- buf.read(bytes, 0, bytes.length);
- buf.close();
+ try (BufferedInputStream buf = new BufferedInputStream(stream)) {
+ //noinspection ResultOfMethodCallIgnored
+ buf.read(bytes, 0, bytes.length);
+ }
return bytes;
} catch (IOException e) {
throw new RuntimeException("Error while trying to read input stream!");
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java
index 1a1f758d69d..5284bbcff17 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java
@@ -67,8 +67,8 @@ public static void loadGeyserLocale(String locale) {
// Load the locale
if (localeStream != null) {
Properties localeProp = new Properties();
- try {
- localeProp.load(new InputStreamReader(localeStream, StandardCharsets.UTF_8));
+ try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) {
+ localeProp.load(reader);
} catch (Exception e) {
throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e);
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
index 15a52cf7f9d..db5457244d5 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
@@ -147,6 +147,12 @@ private static void downloadLocale(String locale) {
}
}
} catch (IOException ignored) { }
+
+ if (clientJarInfo == null) {
+ // Likely failed to download
+ GeyserConnector.getInstance().getLogger().debug("Skipping en_US hash check as client jar is null.");
+ return;
+ }
targetHash = clientJarInfo.getSha1();
} else {
curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
@@ -168,9 +174,13 @@ private static void downloadLocale(String locale) {
return;
}
- // Get the hash and download the locale
- String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
- WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
+ try {
+ // Get the hash and download the locale
+ String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
+ WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
+ } catch (Exception e) {
+ GeyserConnector.getInstance().getLogger().error("Unable to download locale file hash", e);
+ }
}
/**
@@ -236,23 +246,22 @@ private static void downloadEN_US(File localeFile) {
WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString());
// Load in the JAR as a zip and extract the file
- ZipFile localeJar = new ZipFile(tmpFilePath.toString());
- InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"));
- FileOutputStream outStream = new FileOutputStream(localeFile);
-
- // Write the file to the locale dir
- byte[] buf = new byte[fileStream.available()];
- int length;
- while ((length = fileStream.read(buf)) != -1) {
- outStream.write(buf, 0, length);
- }
-
- // Flush all changes to disk and cleanup
- outStream.flush();
- outStream.close();
+ try (ZipFile localeJar = new ZipFile(tmpFilePath.toString())) {
+ try (InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"))) {
+ try (FileOutputStream outStream = new FileOutputStream(localeFile)) {
+
+ // Write the file to the locale dir
+ byte[] buf = new byte[fileStream.available()];
+ int length;
+ while ((length = fileStream.read(buf)) != -1) {
+ outStream.write(buf, 0, length);
+ }
- fileStream.close();
- localeJar.close();
+ // Flush all changes to disk and cleanup
+ outStream.flush();
+ }
+ }
+ }
// Store the latest jar hash
FileUtils.writeFile(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toString(), clientJarInfo.getSha1().toCharArray());
diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
index bcb1ffd503c..91d1b782e66 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
@@ -30,6 +30,8 @@
import java.io.File;
import java.util.HashMap;
import java.util.Map;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
@@ -70,10 +72,12 @@ public static void loadPacks() {
pack.sha256 = FileUtils.calculateSHA256(file);
+ Stream extends ZipEntry> stream = null;
try {
ZipFile zip = new ZipFile(file);
- zip.stream().forEach((x) -> {
+ stream = zip.stream();
+ stream.forEach((x) -> {
if (x.getName().contains("manifest.json")) {
try {
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
@@ -94,6 +98,10 @@ public static void loadPacks() {
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
e.printStackTrace();
+ } finally {
+ if (stream != null) {
+ stream.close();
+ }
}
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
index 77afda53d1d..c01378d0012 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
@@ -147,7 +147,7 @@ public static boolean handleSettingsForm(GeyserSession session, String response)
}
if (Boolean.class.equals(gamerule.getType())) {
- Boolean value = settingsResponse.getToggleResponses().get(offset).booleanValue();
+ boolean value = settingsResponse.getToggleResponses().get(offset);
if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) {
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java
index 874fb062021..ba008da4176 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java
@@ -88,8 +88,7 @@ public static void downloadFile(String reqURL, String fileLocation) {
}
public static String post(String reqURL, String postContent) throws IOException {
- URL url = null;
- url = new URL(reqURL);
+ URL url = new URL(reqURL);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "text/plain");
@@ -112,7 +111,7 @@ public static String post(String reqURL, String postContent) throws IOException
*/
private static String connectionToString(HttpURLConnection con) throws IOException {
// Send the request (we dont use this but its required for getErrorStream() to work)
- int code = con.getResponseCode();
+ con.getResponseCode();
// Read the error message if there is one if not just read normally
InputStream inputStream = con.getErrorStream();
@@ -120,17 +119,17 @@ private static String connectionToString(HttpURLConnection con) throws IOExcepti
inputStream = con.getInputStream();
}
- BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
- String inputLine;
- StringBuffer content = new StringBuffer();
+ StringBuilder content = new StringBuilder();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) {
+ String inputLine;
- while ((inputLine = in.readLine()) != null) {
- content.append(inputLine);
- content.append("\n");
- }
+ while ((inputLine = in.readLine()) != null) {
+ content.append(inputLine);
+ content.append("\n");
+ }
- in.close();
- con.disconnect();
+ con.disconnect();
+ }
return content.toString();
}
diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml
index 07b73173e5b..ce202a3c1f1 100644
--- a/connector/src/main/resources/config.yml
+++ b/connector/src/main/resources/config.yml
@@ -18,10 +18,19 @@ bedrock:
# This option is for the plugin version only.
clone-remote-port: false
# The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true
- motd1: "GeyserMC"
- motd2: "Another GeyserMC forced host."
+ # If either of these are empty, the respective string will default to "Geyser"
+ motd1: "Geyser"
+ 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,
@@ -81,7 +90,11 @@ legacy-ping-passthrough: false
# Increase if you are getting BrokenPipe errors.
ping-passthrough-interval: 3
-# Maximum amount of players that can connect
+# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate
+# ping, it may also cause players to time out more easily.
+forward-player-ping: false
+
+# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count.
max-players: 100
# If debug messages should be sent through console