disconnectReason = result.getReasonComponent();
- if (disconnectReason.isPresent()) {
- // The component is guaranteed to be provided if the connection was denied.
- inbound.disconnect(disconnectReason.get());
- return;
- }
-
- inbound.loginEventFired(() -> {
- if (mcConnection.isClosed()) {
- // The player was disconnected
- return;
- }
-
- mcConnection.eventLoop().execute(() -> {
- if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode()
- || result.isOnlineModeAllowed())) {
- // Request encryption.
- EncryptionRequest request = generateEncryptionRequest();
- this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
- mcConnection.write(request);
- this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
- } else {
- mcConnection.setSessionHandler(new AuthSessionHandler(
- server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false
- ));
- }
- });
- });
- }, mcConnection.eventLoop())
- .exceptionally((ex) -> {
- logger.error("Exception in pre-login stage", ex);
- return null;
});
+ });
+ }, mcConnection.eventLoop()).exceptionally((ex) -> {
+ logger.error("Exception in pre-login stage", ex);
+ return null;
+ });
return true;
}
@@ -246,13 +245,12 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
}
}
// All went well, initialize the session.
- mcConnection.setSessionHandler(new AuthSessionHandler(
- server, inbound, profile, true
- ));
+ mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
+ new AuthSessionHandler(server, inbound, profile, true));
} else if (profileResponse.getStatusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy.
- inbound.disconnect(Component.translatable("velocity.error.online-mode-only",
- NamedTextColor.RED));
+ inbound.disconnect(
+ Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED));
} else {
// Something else went wrong
logger.error(
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java
index 5d7542170..19b09c122 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java
@@ -76,9 +76,9 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
},
/**
- * The Mod list is sent to the server, captured by Velocity. Transition to
- * {@link #WAITING_SERVER_DATA} when an ACK is sent, which indicates to the server to start
- * sending state data.
+ * The Mod list is sent to the server, captured by Velocity. Transition to {@link
+ * #WAITING_SERVER_DATA} when an ACK is sent, which indicates to the server to start sending state
+ * data.
*/
MOD_LIST(LegacyForgeConstants.ACK_DISCRIMINATOR) {
@Override
@@ -138,11 +138,10 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
/**
* The handshake is complete. The handshake can be reset.
*
- * Note that a successful connection to a server does not mean that
- * we will be in this state. After a handshake reset, if the next server is vanilla we will still
- * be in the {@link #NOT_STARTED} phase, which means we must NOT send a reset packet. This is
- * handled by overriding the {@link #resetConnectionPhase(ConnectedPlayer)} in this element (it is
- * usually a no-op).
+ * Note that a successful connection to a server does not mean that we will be in this state.
+ * After a handshake reset, if the next server is vanilla we will still be in the {@link
+ * #NOT_STARTED} phase, which means we must NOT send a reset packet. This is handled by overriding
+ * the {@link #resetConnectionPhase(ConnectedPlayer)} in this element (it is usually a no-op).
*/
COMPLETE(null) {
@Override
@@ -165,7 +164,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
// just in case the timing is awful
player.sendKeepAlive();
- MinecraftSessionHandler handler = backendConn.getSessionHandler();
+ MinecraftSessionHandler handler = backendConn.getActiveSessionHandler();
if (handler instanceof ClientPlaySessionHandler) {
((ClientPlaySessionHandler) handler).flushQueuedMessages();
}
@@ -182,8 +181,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
*
* @param packetToAdvanceOn The ID of the packet discriminator that indicates that the client has
* moved onto a new phase, and as such, Velocity should do so too
- * (inspecting {@link #nextPhase()}. A null indicates there is no further
- * phase to transition to.
+ * (inspecting {@link #nextPhase()}. A null indicates there is
+ * no further phase to transition to.
*/
LegacyForgeHandshakeClientPhase(Integer packetToAdvanceOn) {
this.packetToAdvanceOn = packetToAdvanceOn;
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java
new file mode 100644
index 000000000..1bca74515
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.connection.registry;
+
+import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
+import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
+import net.kyori.adventure.key.Key;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Holds the registry data that is sent
+ * to the client during the config stage.
+ */
+public class ClientConfigData {
+
+ private final @Nullable VelocityResourcePackInfo resourcePackInfo;
+ private final DataTag tag;
+ private final RegistrySync registry;
+ private final Key[] features;
+ private final String brand;
+
+ private ClientConfigData(@Nullable VelocityResourcePackInfo resourcePackInfo, DataTag tag,
+ RegistrySync registry, Key[] features, String brand) {
+ this.resourcePackInfo = resourcePackInfo;
+ this.tag = tag;
+ this.registry = registry;
+ this.features = features;
+ this.brand = brand;
+ }
+
+ public RegistrySync getRegistry() {
+ return registry;
+ }
+
+ public DataTag getTag() {
+ return tag;
+ }
+
+ public Key[] getFeatures() {
+ return features;
+ }
+
+ public @Nullable VelocityResourcePackInfo getResourcePackInfo() {
+ return resourcePackInfo;
+ }
+
+ public String getBrand() {
+ return brand;
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @return ClientConfigData.Builder
+ */
+ public static ClientConfigData.Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for ClientConfigData.
+ */
+ public static class Builder {
+ private VelocityResourcePackInfo resourcePackInfo;
+ private DataTag tag;
+ private RegistrySync registry;
+ private Key[] features;
+ private String brand;
+
+ private Builder() {
+ }
+
+ /**
+ * Clears the builder.
+ */
+ public void clear() {
+ this.resourcePackInfo = null;
+ this.tag = null;
+ this.registry = null;
+ this.features = null;
+ this.brand = null;
+ }
+
+ public Builder resourcePack(@Nullable VelocityResourcePackInfo resourcePackInfo) {
+ this.resourcePackInfo = resourcePackInfo;
+ return this;
+ }
+
+ public Builder dataTag(DataTag tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ public Builder registry(RegistrySync registry) {
+ this.registry = registry;
+ return this;
+ }
+
+ public Builder features(Key[] features) {
+ this.features = features;
+ return this;
+ }
+
+ public Builder brand(String brand) {
+ this.brand = brand;
+ return this;
+ }
+
+ public ClientConfigData build() {
+ return new ClientConfigData(resourcePackInfo, tag, registry, features, brand);
+ }
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java
new file mode 100644
index 000000000..9a7d0de73
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.connection.registry;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.key.Keyed;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Represents a data tag.
+ */
+public class DataTag {
+ private final ImmutableList entrySets;
+
+ public DataTag(ImmutableList entrySets) {
+ this.entrySets = entrySets;
+ }
+
+ /**
+ * Returns the entry sets.
+ *
+ * @return List of entry sets
+ */
+ public List getEntrySets() {
+ return entrySets;
+ }
+
+ /**
+ * Represents a data tag set.
+ */
+ public static class Set implements Keyed {
+
+ private final Key key;
+ private final ImmutableList entries;
+
+ public Set(Key key, ImmutableList entries) {
+ this.key = key;
+ this.entries = entries;
+ }
+
+ /**
+ * Returns the entries.
+ *
+ * @return List of entries
+ */
+ public List getEntries() {
+ return entries;
+ }
+
+ @Override
+ public @NotNull Key key() {
+ return key;
+ }
+ }
+
+ /**
+ * Represents a data tag entry.
+ */
+ public static class Entry implements Keyed {
+
+ private final Key key;
+ private final int[] elements;
+
+ public Entry(Key key, int[] elements) {
+ this.key = key;
+ this.elements = elements;
+ }
+
+ public int[] getElements() {
+ return elements;
+ }
+
+ @Override
+ public @NotNull Key key() {
+ return key;
+ }
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java
index 08bc04c07..fe360d4d6 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java
@@ -35,8 +35,7 @@ import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
-import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
-import net.kyori.adventure.translation.GlobalTranslator;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
@@ -55,6 +54,8 @@ import org.jline.reader.LineReaderBuilder;
public final class VelocityConsole extends SimpleTerminalConsole implements ConsoleCommandSource {
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
+ private static final ComponentLogger componentLogger = ComponentLogger
+ .logger(VelocityConsole.class);
private final VelocityServer server;
private PermissionFunction permissionFunction = ALWAYS_TRUE;
@@ -72,9 +73,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
@Override
public void sendMessage(@NonNull Identity identity, @NonNull Component message,
@NonNull MessageType messageType) {
- Component translated = GlobalTranslator.render(message, ClosestLocaleMatcher.INSTANCE
- .lookupClosest(Locale.getDefault()));
- logger.info(LegacyComponentSerializer.legacySection().serialize(translated));
+ componentLogger.info(message);
}
@Override
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java
index 62f9681eb..a55147570 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java
@@ -318,8 +318,7 @@ public class VelocityEventManager implements EventManager {
if (returnType != void.class && continuationType == Continuation.class) {
errors.add("method return type must be void if a continuation parameter is provided");
} else if (returnType != void.class && returnType != EventTask.class) {
- errors.add("method return type must be void, EventTask, "
- + "EventTask.Basic or EventTask.WithContinuation");
+ errors.add("method return type must be void or EventTask");
} else if (returnType == EventTask.class) {
asyncType = AsyncType.SOMETIMES;
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java
index 4126c9b12..ddd40b4a7 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java
@@ -216,9 +216,11 @@ public final class ConnectionManager {
}
/**
- * Closes all endpoints.
+ * Closes all the currently registered endpoints.
+ *
+ * @param interrupt should closing forward interruptions
*/
- public void shutdown() {
+ public void closeEndpoints(boolean interrupt) {
for (final Map.Entry entry : this.endpoints.entrySet()) {
final SocketAddress address = entry.getKey();
final Endpoint endpoint = entry.getValue();
@@ -227,14 +229,26 @@ public final class ConnectionManager {
// should have a chance to be notified before the server stops accepting connections.
server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join();
- try {
- LOGGER.info("Closing endpoint {}", address);
- endpoint.getChannel().close().sync();
- } catch (final InterruptedException e) {
- LOGGER.info("Interrupted whilst closing endpoint", e);
- Thread.currentThread().interrupt();
+ LOGGER.info("Closing endpoint {}", address);
+ if (interrupt) {
+ try {
+ endpoint.getChannel().close().sync();
+ } catch (final InterruptedException e) {
+ LOGGER.info("Interrupted whilst closing endpoint", e);
+ Thread.currentThread().interrupt();
+ }
+ } else {
+ endpoint.getChannel().close().syncUninterruptibly();
}
}
+ this.endpoints.clear();
+ }
+
+ /**
+ * Closes all endpoints.
+ */
+ public void shutdown() {
+ this.closeEndpoints(true);
this.resolver.shutdown();
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java
index dff8d2236..27ec4ba8b 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java
@@ -35,6 +35,7 @@ public class Connections {
public static final String MINECRAFT_DECODER = "minecraft-decoder";
public static final String MINECRAFT_ENCODER = "minecraft-encoder";
public static final String READ_TIMEOUT = "read-timeout";
+ public static final String PLAY_PACKET_QUEUE = "play-packet-queue";
private Connections() {
throw new AssertionError();
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java
index 62f38ebd7..ef8e6b1cb 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java
@@ -29,6 +29,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
@@ -67,7 +68,8 @@ public class ServerChannelInitializer extends ChannelInitializer {
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.CLIENTBOUND));
final MinecraftConnection connection = new MinecraftConnection(ch, this.server);
- connection.setSessionHandler(new HandshakeSessionHandler(connection, this.server));
+ connection.setActiveSessionHandler(StateRegistry.HANDSHAKE,
+ new HandshakeSessionHandler(connection, this.server));
ch.pipeline().addLast(Connections.HANDLER, connection);
if (this.server.getConfiguration().isProxyProtocol()) {
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java
index a862a2b53..2c6f752f7 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java
@@ -94,7 +94,7 @@ public class VelocityPluginManager implements PluginManager {
for (Path path : stream) {
try {
found.add(loader.loadCandidate(path));
- } catch (Exception e) {
+ } catch (Throwable e) {
logger.error("Unable to load plugin {}", path, e);
}
}
@@ -126,7 +126,7 @@ public class VelocityPluginManager implements PluginManager {
VelocityPluginContainer container = new VelocityPluginContainer(realPlugin);
pluginContainers.put(container, loader.createModule(container));
loadedPluginsById.add(realPlugin.getId());
- } catch (Exception e) {
+ } catch (Throwable e) {
logger.error("Can't create module for plugin {}", candidate.getId(), e);
}
}
@@ -153,7 +153,7 @@ public class VelocityPluginManager implements PluginManager {
try {
loader.createPlugin(container, plugin.getValue(), commonModule);
- } catch (Exception e) {
+ } catch (Throwable e) {
logger.error("Can't create plugin {}", description.getId(), e);
continue;
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java
index 40b00df7c..5e757708e 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java
@@ -71,4 +71,8 @@ public class VelocityPluginContainer implements PluginContainer {
return this.service;
}
+
+ public boolean hasExecutorService() {
+ return this.service != null;
+ }
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java
index 8e59816af..a6331dd48 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java
@@ -26,6 +26,7 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,6 +50,7 @@ class VelocityPluginModule implements Module {
binder.bind(description.getMainClass()).in(Scopes.SINGLETON);
binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId()));
+ binder.bind(ComponentLogger.class).toInstance(ComponentLogger.logger(description.getId()));
binder.bind(Path.class).annotatedWith(DataDirectory.class)
.toInstance(basePluginPath.resolve(description.getId()));
binder.bind(PluginDescription.class).toInstance(description);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
index 97b40def7..e65ee056b 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
@@ -41,6 +41,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
@@ -202,8 +203,7 @@ public enum ProtocolUtils {
buf.readableBytes());
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
buf.skipBytes(length);
- checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)",
- str.length(), cap);
+ checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", str.length(), cap);
return str;
}
@@ -219,6 +219,59 @@ public enum ProtocolUtils {
buf.writeCharSequence(str, StandardCharsets.UTF_8);
}
+ /**
+ * Reads a standard Mojang Text namespaced:key from the buffer.
+ *
+ * @param buf the buffer to read from
+ * @return the decoded key
+ */
+ public static Key readKey(ByteBuf buf) {
+ return Key.key(readString(buf), Key.DEFAULT_SEPARATOR);
+ }
+
+ /**
+ * Writes a standard Mojang Text namespaced:key to the buffer.
+ *
+ * @param buf the buffer to write to
+ * @param key the key to write
+ */
+ public static void writeKey(ByteBuf buf, Key key) {
+ writeString(buf, key.asString());
+ }
+
+ /**
+ * Reads a standard Mojang Text namespaced:key array from the buffer.
+ *
+ * @param buf the buffer to read from
+ * @return the decoded key array
+ */
+ public static Key[] readKeyArray(ByteBuf buf) {
+ int length = readVarInt(buf);
+ checkFrame(length >= 0, "Got a negative-length array (%s)", length);
+ checkFrame(buf.isReadable(length),
+ "Trying to read an array that is too long (wanted %s, only have %s)", length,
+ buf.readableBytes());
+ Key[] ret = new Key[length];
+
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = ProtocolUtils.readKey(buf);
+ }
+ return ret;
+ }
+
+ /**
+ * Writes a standard Mojang Text namespaced:key array to the buffer.
+ *
+ * @param buf the buffer to write to
+ * @param keys the keys to write
+ */
+ public static void writeKeyArray(ByteBuf buf, Key[] keys) {
+ writeVarInt(buf, keys.length);
+ for (Key key : keys) {
+ writeKey(buf, key);
+ }
+ }
+
public static byte[] readByteArray(ByteBuf buf) {
return readByteArray(buf, DEFAULT_MAX_STRING_SIZE);
}
@@ -368,6 +421,38 @@ public enum ProtocolUtils {
}
}
+ /**
+ * Reads an Integer array from the {@code buf}.
+ *
+ * @param buf the buffer to read from
+ * @return the Integer array from the buffer
+ */
+ public static int[] readVarIntArray(ByteBuf buf) {
+ int length = readVarInt(buf);
+ checkFrame(length >= 0, "Got a negative-length array (%s)", length);
+ checkFrame(buf.isReadable(length),
+ "Trying to read an array that is too long (wanted %s, only have %s)", length,
+ buf.readableBytes());
+ int[] ret = new int[length];
+ for (int i = 0; i < length; i++) {
+ ret[i] = readVarInt(buf);
+ }
+ return ret;
+ }
+
+ /**
+ * Writes an Integer Array to the {@code buf}.
+ *
+ * @param buf the buffer to write to
+ * @param intArray the array to write
+ */
+ public static void writeVarIntArray(ByteBuf buf, int[] intArray) {
+ writeVarInt(buf, intArray.length);
+ for (int i = 0; i < intArray.length; i++) {
+ writeVarInt(buf, intArray[i]);
+ }
+ }
+
/**
* Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer.
*
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
index 9cb4aacdc..8ebb7eadc 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
@@ -33,6 +33,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4;
+import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@@ -55,8 +56,10 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.JoinGame;
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
+import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
+import com.velocitypowered.proxy.protocol.packet.PingIdentify;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
@@ -72,6 +75,7 @@ import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
+import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgement;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
@@ -79,6 +83,11 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
+import com.velocitypowered.proxy.protocol.packet.config.ActiveFeatures;
+import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
+import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
+import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
+import com.velocitypowered.proxy.protocol.packet.config.TagsUpdate;
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@@ -98,9 +107,7 @@ import java.util.Objects;
import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable;
-/**
- * Registry of all Minecraft protocol states and the packets for each state.
- */
+/** Registry of all Minecraft protocol states and the packets for each state. */
public enum StateRegistry {
HANDSHAKE {
@@ -111,15 +118,46 @@ public enum StateRegistry {
},
STATUS {
{
- serverbound.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
- map(0x00, MINECRAFT_1_7_2, false));
- serverbound.register(StatusPing.class, StatusPing::new,
- map(0x01, MINECRAFT_1_7_2, false));
+ serverbound.register(
+ StatusRequest.class, () -> StatusRequest.INSTANCE, map(0x00, MINECRAFT_1_7_2, false));
+ serverbound.register(StatusPing.class, StatusPing::new, map(0x01, MINECRAFT_1_7_2, false));
- clientbound.register(StatusResponse.class, StatusResponse::new,
- map(0x00, MINECRAFT_1_7_2, false));
- clientbound.register(StatusPing.class, StatusPing::new,
- map(0x01, MINECRAFT_1_7_2, false));
+ clientbound.register(
+ StatusResponse.class, StatusResponse::new, map(0x00, MINECRAFT_1_7_2, false));
+ clientbound.register(StatusPing.class, StatusPing::new, map(0x01, MINECRAFT_1_7_2, false));
+ }
+ },
+ CONFIG {
+ {
+ serverbound.register(
+ ClientSettings.class, ClientSettings::new, map(0x00, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ PluginMessage.class, PluginMessage::new, map(0x01, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ FinishedUpdate.class, FinishedUpdate::new, map(0x02, MINECRAFT_1_20_2, false));
+ serverbound.register(KeepAlive.class, KeepAlive::new, map(0x03, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ PingIdentify.class, PingIdentify::new, map(0x04, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ ResourcePackResponse.class,
+ ResourcePackResponse::new,
+ map(0x05, MINECRAFT_1_20_2, false));
+
+ clientbound.register(
+ PluginMessage.class, PluginMessage::new, map(0x00, MINECRAFT_1_20_2, false));
+ clientbound.register(Disconnect.class, Disconnect::new, map(0x01, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ FinishedUpdate.class, FinishedUpdate::new, map(0x02, MINECRAFT_1_20_2, false));
+ clientbound.register(KeepAlive.class, KeepAlive::new, map(0x03, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ PingIdentify.class, PingIdentify::new, map(0x04, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ RegistrySync.class, RegistrySync::new, map(0x05, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ ResourcePackRequest.class, ResourcePackRequest::new, map(0x06, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ ActiveFeatures.class, ActiveFeatures::new, map(0x07, MINECRAFT_1_20_2, false));
+ clientbound.register(TagsUpdate.class, TagsUpdate::new, map(0x08, MINECRAFT_1_20_2, false));
}
},
PLAY {
@@ -137,13 +175,20 @@ public enum StateRegistry {
map(0x08, MINECRAFT_1_19, false),
map(0x09, MINECRAFT_1_19_1, false),
map(0x08, MINECRAFT_1_19_3, false),
- map(0x09, MINECRAFT_1_19_4, false));
- serverbound.register(LegacyChat.class, LegacyChat::new,
+ map(0x09, MINECRAFT_1_19_4, false),
+ map(0x0A, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ LegacyChat.class,
+ LegacyChat::new,
map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false),
map(0x03, MINECRAFT_1_12, false),
map(0x02, MINECRAFT_1_12_1, false),
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
+ serverbound.register(
+ ChatAcknowledgement.class,
+ ChatAcknowledgement::new,
+ map(0x03, MINECRAFT_1_19_3, false));
serverbound.register(KeyedPlayerCommand.class, KeyedPlayerCommand::new,
map(0x03, MINECRAFT_1_19, false),
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
@@ -152,9 +197,13 @@ public enum StateRegistry {
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(SessionPlayerCommand.class, SessionPlayerCommand::new,
map(0x04, MINECRAFT_1_19_3, false));
- serverbound.register(SessionPlayerChat.class, SessionPlayerChat::new,
- map(0x05, MINECRAFT_1_19_3, false));
- serverbound.register(ClientSettings.class, ClientSettings::new,
+ serverbound.register(
+ SessionPlayerChat.class,
+ SessionPlayerChat::new,
+ map(0x05, MINECRAFT_1_19_3, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ ClientSettings.class,
+ ClientSettings::new,
map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false),
map(0x05, MINECRAFT_1_12, false),
@@ -163,8 +212,11 @@ public enum StateRegistry {
map(0x07, MINECRAFT_1_19, false),
map(0x08, MINECRAFT_1_19_1, false),
map(0x07, MINECRAFT_1_19_3, false),
- map(0x08, MINECRAFT_1_19_4, false));
- serverbound.register(PluginMessage.class, PluginMessage::new,
+ map(0x08, MINECRAFT_1_19_4, false),
+ map(0x09, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ PluginMessage.class,
+ PluginMessage::new,
map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false),
map(0x0A, MINECRAFT_1_12, false),
@@ -175,8 +227,11 @@ public enum StateRegistry {
map(0x0C, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_1, false),
map(0x0C, MINECRAFT_1_19_3, false),
- map(0x0D, MINECRAFT_1_19_4, false));
- serverbound.register(KeepAlive.class, KeepAlive::new,
+ map(0x0D, MINECRAFT_1_19_4, false),
+ map(0x0F, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ KeepAlive.class,
+ KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false),
map(0x0C, MINECRAFT_1_12, false),
@@ -188,8 +243,11 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_19, false),
map(0x12, MINECRAFT_1_19_1, false),
map(0x11, MINECRAFT_1_19_3, false),
- map(0x12, MINECRAFT_1_19_4, false));
- serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new,
+ map(0x12, MINECRAFT_1_19_4, false),
+ map(0x14, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ ResourcePackResponse.class,
+ ResourcePackResponse::new,
map(0x19, MINECRAFT_1_8, false),
map(0x16, MINECRAFT_1_9, false),
map(0x18, MINECRAFT_1_12, false),
@@ -198,16 +256,24 @@ public enum StateRegistry {
map(0x20, MINECRAFT_1_16, false),
map(0x21, MINECRAFT_1_16_2, false),
map(0x23, MINECRAFT_1_19, false),
- map(0x24, MINECRAFT_1_19_1, false));
+ map(0x24, MINECRAFT_1_19_1, false),
+ map(0x27, MINECRAFT_1_20_2, false));
+ serverbound.register(
+ FinishedUpdate.class, FinishedUpdate::new, map(0x0B, MINECRAFT_1_20_2, false));
- clientbound.register(BossBar.class, BossBar::new,
+ clientbound.register(
+ BossBar.class,
+ BossBar::new,
map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false),
map(0x0C, MINECRAFT_1_16, false),
map(0x0D, MINECRAFT_1_17, false),
map(0x0A, MINECRAFT_1_19, false),
- map(0x0B, MINECRAFT_1_19_4, false));
- clientbound.register(LegacyChat.class, LegacyChat::new,
+ map(0x0B, MINECRAFT_1_19_4, false),
+ map(0x0A, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ LegacyChat.class,
+ LegacyChat::new,
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true),
@@ -224,8 +290,11 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_17, false),
map(0x0E, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_3, false),
- map(0x0F, MINECRAFT_1_19_4, false));
- clientbound.register(AvailableCommands.class, AvailableCommands::new,
+ map(0x0F, MINECRAFT_1_19_4, false),
+ map(0x10, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ AvailableCommands.class,
+ AvailableCommands::new,
map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false),
map(0x11, MINECRAFT_1_16, false),
@@ -233,8 +302,11 @@ public enum StateRegistry {
map(0x12, MINECRAFT_1_17, false),
map(0x0F, MINECRAFT_1_19, false),
map(0x0E, MINECRAFT_1_19_3, false),
- map(0x10, MINECRAFT_1_19_4, false));
- clientbound.register(PluginMessage.class, PluginMessage::new,
+ map(0x10, MINECRAFT_1_19_4, false),
+ map(0x11, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ PluginMessage.class,
+ PluginMessage::new,
map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false),
map(0x19, MINECRAFT_1_13, false),
@@ -246,8 +318,11 @@ public enum StateRegistry {
map(0x15, MINECRAFT_1_19, false),
map(0x16, MINECRAFT_1_19_1, false),
map(0x15, MINECRAFT_1_19_3, false),
- map(0x17, MINECRAFT_1_19_4, false));
- clientbound.register(Disconnect.class, Disconnect::new,
+ map(0x17, MINECRAFT_1_19_4, false),
+ map(0x18, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ Disconnect.class,
+ Disconnect::new,
map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false),
map(0x1B, MINECRAFT_1_13, false),
@@ -259,8 +334,11 @@ public enum StateRegistry {
map(0x17, MINECRAFT_1_19, false),
map(0x19, MINECRAFT_1_19_1, false),
map(0x17, MINECRAFT_1_19_3, false),
- map(0x1A, MINECRAFT_1_19_4, false));
- clientbound.register(KeepAlive.class, KeepAlive::new,
+ map(0x1A, MINECRAFT_1_19_4, false),
+ map(0x1B, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ KeepAlive.class,
+ KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false),
map(0x21, MINECRAFT_1_13, false),
@@ -272,8 +350,11 @@ public enum StateRegistry {
map(0x1E, MINECRAFT_1_19, false),
map(0x20, MINECRAFT_1_19_1, false),
map(0x1F, MINECRAFT_1_19_3, false),
- map(0x23, MINECRAFT_1_19_4, false));
- clientbound.register(JoinGame.class, JoinGame::new,
+ map(0x23, MINECRAFT_1_19_4, false),
+ map(0x24, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ JoinGame.class,
+ JoinGame::new,
map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false),
map(0x25, MINECRAFT_1_13, false),
@@ -285,8 +366,11 @@ public enum StateRegistry {
map(0x23, MINECRAFT_1_19, false),
map(0x25, MINECRAFT_1_19_1, false),
map(0x24, MINECRAFT_1_19_3, false),
- map(0x28, MINECRAFT_1_19_4, false));
- clientbound.register(Respawn.class, Respawn::new,
+ map(0x28, MINECRAFT_1_19_4, false),
+ map(0x29, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ Respawn.class,
+ Respawn::new,
map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true),
map(0x34, MINECRAFT_1_12, true),
@@ -300,8 +384,11 @@ public enum StateRegistry {
map(0x3B, MINECRAFT_1_19, true),
map(0x3E, MINECRAFT_1_19_1, true),
map(0x3D, MINECRAFT_1_19_3, true),
- map(0x41, MINECRAFT_1_19_4, true));
- clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
+ map(0x41, MINECRAFT_1_19_4, true),
+ map(0x43, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ ResourcePackRequest.class,
+ ResourcePackRequest::new,
map(0x48, MINECRAFT_1_8, false),
map(0x32, MINECRAFT_1_9, false),
map(0x33, MINECRAFT_1_12, false),
@@ -315,8 +402,11 @@ public enum StateRegistry {
map(0x3A, MINECRAFT_1_19, false),
map(0x3D, MINECRAFT_1_19_1, false),
map(0x3C, MINECRAFT_1_19_3, false),
- map(0x40, MINECRAFT_1_19_4, false));
- clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
+ map(0x40, MINECRAFT_1_19_4, false),
+ map(0x42, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ HeaderAndFooter.class,
+ HeaderAndFooter::new,
map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_9_4, true),
@@ -331,8 +421,11 @@ public enum StateRegistry {
map(0x60, MINECRAFT_1_19, true),
map(0x63, MINECRAFT_1_19_1, true),
map(0x61, MINECRAFT_1_19_3, true),
- map(0x65, MINECRAFT_1_19_4, true));
- clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new,
+ map(0x65, MINECRAFT_1_19_4, true),
+ map(0x68, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ LegacyTitlePacket.class,
+ LegacyTitlePacket::new,
map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_12, true),
@@ -346,31 +439,46 @@ public enum StateRegistry {
map(0x58, MINECRAFT_1_18, true),
map(0x5B, MINECRAFT_1_19_1, true),
map(0x59, MINECRAFT_1_19_3, true),
- map(0x5D, MINECRAFT_1_19_4, true));
- clientbound.register(TitleTextPacket.class, TitleTextPacket::new,
+ map(0x5D, MINECRAFT_1_19_4, true),
+ map(0x5F, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ TitleTextPacket.class,
+ TitleTextPacket::new,
map(0x59, MINECRAFT_1_17, true),
map(0x5A, MINECRAFT_1_18, true),
map(0x5D, MINECRAFT_1_19_1, true),
map(0x5B, MINECRAFT_1_19_3, true),
- map(0x5F, MINECRAFT_1_19_4, true));
- clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new,
+ map(0x5F, MINECRAFT_1_19_4, true),
+ map(0x61, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ TitleActionbarPacket.class,
+ TitleActionbarPacket::new,
map(0x41, MINECRAFT_1_17, true),
map(0x40, MINECRAFT_1_19, true),
map(0x43, MINECRAFT_1_19_1, true),
map(0x42, MINECRAFT_1_19_3, true),
- map(0x46, MINECRAFT_1_19_4, true));
- clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new,
+ map(0x46, MINECRAFT_1_19_4, true),
+ map(0x48, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ TitleTimesPacket.class,
+ TitleTimesPacket::new,
map(0x5A, MINECRAFT_1_17, true),
map(0x5B, MINECRAFT_1_18, true),
map(0x5E, MINECRAFT_1_19_1, true),
map(0x5C, MINECRAFT_1_19_3, true),
- map(0x60, MINECRAFT_1_19_4, true));
- clientbound.register(TitleClearPacket.class, TitleClearPacket::new,
+ map(0x60, MINECRAFT_1_19_4, true),
+ map(0x62, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ TitleClearPacket.class,
+ TitleClearPacket::new,
map(0x10, MINECRAFT_1_17, true),
map(0x0D, MINECRAFT_1_19, true),
map(0x0C, MINECRAFT_1_19_3, true),
- map(0x0E, MINECRAFT_1_19_4, true));
- clientbound.register(LegacyPlayerListItem.class, LegacyPlayerListItem::new,
+ map(0x0E, MINECRAFT_1_19_4, true),
+ map(0x0F, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ LegacyPlayerListItem.class,
+ LegacyPlayerListItem::new,
map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false),
@@ -384,68 +492,83 @@ public enum StateRegistry {
map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
clientbound.register(RemovePlayerInfo.class, RemovePlayerInfo::new,
map(0x35, MINECRAFT_1_19_3, false),
- map(0x39, MINECRAFT_1_19_4, false));
- clientbound.register(UpsertPlayerInfo.class, UpsertPlayerInfo::new,
+ map(0x39, MINECRAFT_1_19_4, false),
+ map(0x3B, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ UpsertPlayerInfo.class,
+ UpsertPlayerInfo::new,
map(0x36, MINECRAFT_1_19_3, false),
- map(0x3A, MINECRAFT_1_19_4, false));
- clientbound.register(SystemChat.class, SystemChat::new,
+ map(0x3A, MINECRAFT_1_19_4, false),
+ map(0x3C, MINECRAFT_1_20_2, false));
+ clientbound.register(
+ SystemChat.class,
+ SystemChat::new,
map(0x5F, MINECRAFT_1_19, true),
map(0x62, MINECRAFT_1_19_1, true),
map(0x60, MINECRAFT_1_19_3, true),
- map(0x64, MINECRAFT_1_19_4, true));
- clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new,
+ map(0x64, MINECRAFT_1_19_4, true),
+ map(0x67, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ PlayerChatCompletion.class,
+ PlayerChatCompletion::new,
map(0x15, MINECRAFT_1_19_1, true),
map(0x14, MINECRAFT_1_19_3, true),
- map(0x16, MINECRAFT_1_19_4, true));
- clientbound.register(ServerData.class, ServerData::new,
+ map(0x16, MINECRAFT_1_19_4, true),
+ map(0x17, MINECRAFT_1_20_2, true));
+ clientbound.register(
+ ServerData.class,
+ ServerData::new,
map(0x3F, MINECRAFT_1_19, false),
map(0x42, MINECRAFT_1_19_1, false),
map(0x41, MINECRAFT_1_19_3, false),
- map(0x45, MINECRAFT_1_19_4, false));
+ map(0x45, MINECRAFT_1_19_4, false),
+ map(0x47, MINECRAFT_1_20_2, false));
+ clientbound.register(StartUpdate.class, StartUpdate::new, map(0x65, MINECRAFT_1_20_2, false));
}
},
LOGIN {
{
- serverbound.register(ServerLogin.class, ServerLogin::new,
- map(0x00, MINECRAFT_1_7_2, false));
- serverbound.register(EncryptionResponse.class, EncryptionResponse::new,
- map(0x01, MINECRAFT_1_7_2, false));
- serverbound.register(LoginPluginResponse.class, LoginPluginResponse::new,
- map(0x02, MINECRAFT_1_13, false));
- clientbound.register(Disconnect.class, Disconnect::new,
- map(0x00, MINECRAFT_1_7_2, false));
- clientbound.register(EncryptionRequest.class, EncryptionRequest::new,
- map(0x01, MINECRAFT_1_7_2, false));
- clientbound.register(ServerLoginSuccess.class, ServerLoginSuccess::new,
- map(0x02, MINECRAFT_1_7_2, false));
- clientbound.register(SetCompression.class, SetCompression::new,
- map(0x03, MINECRAFT_1_8, false));
- clientbound.register(LoginPluginMessage.class, LoginPluginMessage::new,
- map(0x04, MINECRAFT_1_13, false));
+ serverbound.register(ServerLogin.class, ServerLogin::new, map(0x00, MINECRAFT_1_7_2, false));
+ serverbound.register(
+ EncryptionResponse.class, EncryptionResponse::new, map(0x01, MINECRAFT_1_7_2, false));
+ serverbound.register(
+ LoginPluginResponse.class, LoginPluginResponse::new, map(0x02, MINECRAFT_1_13, false));
+ serverbound.register(
+ LoginAcknowledged.class, LoginAcknowledged::new, map(0x03, MINECRAFT_1_20_2, false));
+
+ clientbound.register(Disconnect.class, Disconnect::new, map(0x00, MINECRAFT_1_7_2, false));
+ clientbound.register(
+ EncryptionRequest.class, EncryptionRequest::new, map(0x01, MINECRAFT_1_7_2, false));
+ clientbound.register(
+ ServerLoginSuccess.class, ServerLoginSuccess::new, map(0x02, MINECRAFT_1_7_2, false));
+ clientbound.register(
+ SetCompression.class, SetCompression::new, map(0x03, MINECRAFT_1_8, false));
+ clientbound.register(
+ LoginPluginMessage.class, LoginPluginMessage::new, map(0x04, MINECRAFT_1_13, false));
}
};
public static final int STATUS_ID = 1;
public static final int LOGIN_ID = 2;
- protected final PacketRegistry clientbound = new PacketRegistry(CLIENTBOUND);
- protected final PacketRegistry serverbound = new PacketRegistry(SERVERBOUND);
+ protected final PacketRegistry clientbound = new PacketRegistry(CLIENTBOUND, this);
+ protected final PacketRegistry serverbound = new PacketRegistry(SERVERBOUND, this);
public StateRegistry.PacketRegistry.ProtocolRegistry getProtocolRegistry(Direction direction,
ProtocolVersion version) {
return (direction == SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version);
}
- /**
- * Packet registry.
- */
+ /** Packet registry. */
public static class PacketRegistry {
private final Direction direction;
+ private final StateRegistry registry;
private final Map versions;
private boolean fallback = true;
- PacketRegistry(Direction direction) {
+ PacketRegistry(Direction direction, StateRegistry registry) {
this.direction = direction;
+ this.registry = registry;
Map mutableVersions = new EnumMap<>(ProtocolVersion.class);
for (ProtocolVersion version : ProtocolVersion.values()) {
@@ -505,19 +628,24 @@ public enum StateRegistry {
}
ProtocolRegistry registry = this.versions.get(protocol);
if (registry == null) {
- throw new IllegalArgumentException("Unknown protocol version "
- + current.protocolVersion);
+ throw new IllegalArgumentException(
+ "Unknown protocol version " + current.protocolVersion);
}
if (registry.packetIdToSupplier.containsKey(current.id)) {
- throw new IllegalArgumentException("Can not register class " + clazz.getSimpleName()
- + " with id " + current.id + " for " + registry.version
- + " because another packet is already registered");
+ throw new IllegalArgumentException(
+ "Can not register class "
+ + clazz.getSimpleName()
+ + " with id "
+ + current.id
+ + " for "
+ + registry.version
+ + " because another packet is already registered");
}
if (registry.packetClassToId.containsKey(clazz)) {
- throw new IllegalArgumentException(clazz.getSimpleName()
- + " is already registered for version " + registry.version);
+ throw new IllegalArgumentException(
+ clazz.getSimpleName() + " is already registered for version " + registry.version);
}
if (!current.encodeOnly) {
@@ -528,9 +656,7 @@ public enum StateRegistry {
}
}
- /**
- * Protocol registry.
- */
+ /** Protocol registry. */
public class ProtocolRegistry {
public final ProtocolVersion version;
@@ -569,18 +695,27 @@ public enum StateRegistry {
final int id = this.packetClassToId.getInt(packet.getClass());
if (id == Integer.MIN_VALUE) {
throw new IllegalArgumentException(String.format(
- "Unable to find id for packet of type %s in %s protocol %s",
- packet.getClass().getName(), PacketRegistry.this.direction, this.version
+ "Unable to find id for packet of type %s in %s protocol %s phase %s",
+ packet.getClass().getName(), PacketRegistry.this.direction,
+ this.version, PacketRegistry.this.registry
));
}
return id;
}
+
+ /**
+ * Checks if the registry contains a packet with the specified {@code id}.
+ *
+ * @param packet the packet to check
+ * @return {@code true} if the packet is registered, {@code false} otherwise
+ */
+ public boolean containsPacket(final MinecraftPacket packet) {
+ return this.packetClassToId.containsKey(packet.getClass());
+ }
}
}
- /**
- * Packet mapping.
- */
+ /** Packet mapping. */
public static final class PacketMapping {
private final int id;
@@ -599,9 +734,12 @@ public enum StateRegistry {
@Override
public String toString() {
return "PacketMapping{"
- + "id=" + id
- + ", protocolVersion=" + protocolVersion
- + ", encodeOnly=" + encodeOnly
+ + "id="
+ + id
+ + ", protocolVersion="
+ + protocolVersion
+ + ", encodeOnly="
+ + encodeOnly
+ '}';
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java
index a19baab70..6e7fb4d4e 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java
@@ -34,7 +34,7 @@ import java.util.List;
public class MinecraftCompressDecoder extends MessageToMessageDecoder {
private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 8 * 1024 * 1024; // 8MiB
- private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 16 * 1024 * 1024; // 16MiB
+ private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 128 * 1024 * 1024; // 128MiB
private static final int UNCOMPRESSED_CAP =
Boolean.getBoolean("velocity.increased-compression-cap")
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java
index b60fbbbc3..f8362cc09 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java
@@ -35,8 +35,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
public static final boolean DEBUG = Boolean.getBoolean("velocity.packet-decode-logging");
private static final QuietRuntimeException DECODE_FAILED =
- new QuietRuntimeException("A packet did not decode successfully (invalid data). If you are a "
- + "developer, launch Velocity with -Dvelocity.packet-decode-logging=true to see more.");
+ new QuietRuntimeException("A packet did not decode successfully (invalid data). For more "
+ + "information, launch Velocity with -Dvelocity.packet-decode-logging=true to see more.");
private final ProtocolUtils.Direction direction;
private StateRegistry state;
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java
index 313b7858d..7133f1d26 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java
@@ -62,4 +62,8 @@ public class MinecraftEncoder extends MessageToByteEncoder {
this.state = state;
this.setProtocolVersion(registry.version);
}
+
+ public ProtocolUtils.Direction getDirection() {
+ return direction;
+ }
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java
new file mode 100644
index 000000000..612de451e
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.netty;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import com.velocitypowered.proxy.protocol.StateRegistry;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.util.ReferenceCountUtil;
+import io.netty.util.internal.PlatformDependent;
+import java.util.Queue;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Queues up any pending PLAY packets while the client is in the CONFIG state.
+ *
+ * Much of the Velocity API (i.e. chat messages) utilize PLAY packets, however the client is
+ * incapable of receiving these packets during the CONFIG state. Certain events such as the
+ * ServerPreConnectEvent may be called during this time, and we need to ensure that any API that
+ * uses these packets will work as expected.
+ *
+ *
This handler will queue up any packets that are sent to the client during this time, and send
+ * them once the client has (re)entered the PLAY state.
+ */
+public class PlayPacketQueueHandler extends ChannelDuplexHandler {
+
+ private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
+ private final Queue queue = PlatformDependent.newMpscQueue();
+
+ /**
+ * Provides registries for client & server bound packets.
+ *
+ * @param version the protocol version
+ */
+ public PlayPacketQueueHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
+ this.registry =
+ StateRegistry.CONFIG.getProtocolRegistry(direction, version);
+ }
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
+ throws Exception {
+ if (!(msg instanceof MinecraftPacket)) {
+ ctx.write(msg, promise);
+ return;
+ }
+
+ // If the packet exists in the CONFIG state, we want to always
+ // ensure that it gets sent out to the client
+ if (this.registry.containsPacket(((MinecraftPacket) msg))) {
+ ctx.write(msg, promise);
+ return;
+ }
+
+ // Otherwise, queue the packet
+ this.queue.offer((MinecraftPacket) msg);
+ }
+
+ @Override
+ public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception {
+ this.releaseQueue(ctx, false);
+
+ super.channelInactive(ctx);
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ this.releaseQueue(ctx, ctx.channel().isActive());
+ }
+
+ private void releaseQueue(ChannelHandlerContext ctx, boolean active) {
+ if (this.queue.isEmpty()) {
+ return;
+ }
+
+ // Send out all the queued packets
+ MinecraftPacket packet;
+ while ((packet = this.queue.poll()) != null) {
+ if (active) {
+ ctx.write(packet, ctx.voidPromise());
+ } else {
+ ReferenceCountUtil.release(packet);
+ }
+ }
+
+ if (active) {
+ ctx.flush();
+ }
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java
index 5c267d195..a42d1ee90 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java
@@ -23,10 +23,10 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Objects;
+
import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientSettings implements MinecraftPacket {
-
private @Nullable String locale;
private byte viewDistance;
private int chatVisibility;
@@ -120,16 +120,10 @@ public class ClientSettings implements MinecraftPacket {
@Override
public String toString() {
- return "ClientSettings{"
- + "locale='" + locale + '\''
- + ", viewDistance=" + viewDistance
- + ", chatVisibility=" + chatVisibility
- + ", chatColors=" + chatColors
- + ", skinParts=" + skinParts
- + ", mainHand=" + mainHand
- + ", chatFilteringEnabled=" + chatFilteringEnabled
- + ", clientListingAllowed=" + clientListingAllowed
- + '}';
+ return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
+ ", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
+ skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + chatFilteringEnabled +
+ ", clientListingAllowed=" + clientListingAllowed + '}';
}
@Override
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java
index 90320f948..938d1190a 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java
@@ -42,6 +42,7 @@ public class JoinGame implements MinecraftPacket {
private int viewDistance; // 1.14+
private boolean reducedDebugInfo;
private boolean showRespawnScreen;
+ private boolean doLimitedCrafting; // 1.20.2+
private ImmutableSet levelNames; // 1.16+
private CompoundBinaryTag registry; // 1.16+
private DimensionInfo dimensionInfo; // 1.16+
@@ -49,6 +50,7 @@ public class JoinGame implements MinecraftPacket {
private short previousGamemode; // 1.16+
private int simulationDistance; // 1.18+
private @Nullable Pair lastDeathPosition; // 1.19+
+ private int portalCooldown; // 1.20+
public int getEntityId() {
return entityId;
@@ -142,6 +144,14 @@ public class JoinGame implements MinecraftPacket {
this.isHardcore = isHardcore;
}
+ public boolean getDoLimitedCrafting() {
+ return doLimitedCrafting;
+ }
+
+ public void setDoLimitedCrafting(boolean doLimitedCrafting) {
+ this.doLimitedCrafting = doLimitedCrafting;
+ }
+
public CompoundBinaryTag getCurrentDimensionData() {
return currentDimensionData;
}
@@ -162,37 +172,38 @@ public class JoinGame implements MinecraftPacket {
this.lastDeathPosition = lastDeathPosition;
}
+ public int getPortalCooldown() {
+ return portalCooldown;
+ }
+
+ public void setPortalCooldown(int portalCooldown) {
+ this.portalCooldown = portalCooldown;
+ }
+
public CompoundBinaryTag getRegistry() {
return registry;
}
@Override
public String toString() {
- return "JoinGame{"
- + "entityId=" + entityId
- + ", gamemode=" + gamemode
- + ", dimension=" + dimension
- + ", partialHashedSeed=" + partialHashedSeed
- + ", difficulty=" + difficulty
- + ", isHardcore=" + isHardcore
- + ", maxPlayers=" + maxPlayers
- + ", levelType='" + levelType + '\''
- + ", viewDistance=" + viewDistance
- + ", reducedDebugInfo=" + reducedDebugInfo
- + ", showRespawnScreen=" + showRespawnScreen
- + ", levelNames=" + levelNames
- + ", registry='" + registry + '\''
- + ", dimensionInfo='" + dimensionInfo + '\''
- + ", currentDimensionData='" + currentDimensionData + '\''
- + ", previousGamemode=" + previousGamemode
- + ", simulationDistance=" + simulationDistance
- + ", lastDeathPosition='" + lastDeathPosition + '\''
- + '}';
+ return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" +
+ dimension + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty +
+ ", isHardcore=" + isHardcore + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType +
+ '\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo +
+ ", showRespawnScreen=" + showRespawnScreen + ", doLimitedCrafting=" + doLimitedCrafting +
+ ", levelNames=" + levelNames + ", registry='" + registry + '\'' + ", dimensionInfo='" +
+ dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
+ ", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
+ ", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
- if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
+ // haha funny, they made 1.20.2 more complicated
+ this.decode1202Up(buf, version);
+ } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
this.decode116Up(buf, version);
@@ -279,11 +290,52 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
+
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
+ this.portalCooldown = ProtocolUtils.readVarInt(buf);
+ }
+ }
+
+ private void decode1202Up(ByteBuf buf, ProtocolVersion version) {
+ this.entityId = buf.readInt();
+ this.isHardcore = buf.readBoolean();
+
+ this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
+
+ this.maxPlayers = ProtocolUtils.readVarInt(buf);
+
+ this.viewDistance = ProtocolUtils.readVarInt(buf);
+ this.simulationDistance = ProtocolUtils.readVarInt(buf);
+
+ this.reducedDebugInfo = buf.readBoolean();
+ this.showRespawnScreen = buf.readBoolean();
+ this.doLimitedCrafting = buf.readBoolean();
+
+ String dimensionIdentifier = ProtocolUtils.readString(buf);
+ String levelName = ProtocolUtils.readString(buf);
+ this.partialHashedSeed = buf.readLong();
+
+ this.gamemode = buf.readByte();
+ this.previousGamemode = buf.readByte();
+
+ boolean isDebug = buf.readBoolean();
+ boolean isFlat = buf.readBoolean();
+ this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
+
+ // optional death location
+ if (buf.readBoolean()) {
+ this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
+ }
+
+ this.portalCooldown = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
- if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
+ // haha funny, they made 1.20.2 more complicated
+ this.encode1202Up(buf, version);
+ } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
this.encode116Up(buf, version);
@@ -376,6 +428,47 @@ public class JoinGame implements MinecraftPacket {
buf.writeBoolean(false);
}
}
+
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
+ ProtocolUtils.writeVarInt(buf, portalCooldown);
+ }
+ }
+
+ private void encode1202Up(ByteBuf buf, ProtocolVersion version) {
+ buf.writeInt(entityId);
+ buf.writeBoolean(isHardcore);
+
+ ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new));
+
+ ProtocolUtils.writeVarInt(buf, maxPlayers);
+
+ ProtocolUtils.writeVarInt(buf, viewDistance);
+ ProtocolUtils.writeVarInt(buf, simulationDistance);
+
+ buf.writeBoolean(reducedDebugInfo);
+ buf.writeBoolean(showRespawnScreen);
+ buf.writeBoolean(doLimitedCrafting);
+
+ ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
+ ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
+ buf.writeLong(partialHashedSeed);
+
+ buf.writeByte(gamemode);
+ buf.writeByte(previousGamemode);
+
+ buf.writeBoolean(dimensionInfo.isDebugType());
+ buf.writeBoolean(dimensionInfo.isFlat());
+
+ // optional death location
+ if (lastDeathPosition != null) {
+ buf.writeBoolean(true);
+ ProtocolUtils.writeString(buf, lastDeathPosition.key());
+ buf.writeLong(lastDeathPosition.value());
+ } else {
+ buf.writeBoolean(false);
+ }
+
+ ProtocolUtils.writeVarInt(buf, portalCooldown);
}
@Override
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledged.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledged.java
new file mode 100644
index 000000000..1f7941004
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledged.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+
+public class LoginAcknowledged implements MinecraftPacket {
+
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ }
+
+ @Override
+ public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion version) {
+ return 0;
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PingIdentify.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PingIdentify.java
new file mode 100644
index 000000000..2d3d9b5da
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PingIdentify.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018-2021 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+
+public class PingIdentify implements MinecraftPacket {
+
+ private int id;
+
+ @Override
+ public String toString() {
+ return "Ping{" + "id=" + id + '}';
+ }
+
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
+ id = buf.readInt();
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
+ buf.writeInt(id);
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java
index 641407f9f..c80b97c16 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java
@@ -17,17 +17,23 @@
package com.velocitypowered.proxy.protocol.packet;
+import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
+import java.util.regex.Pattern;
+
public class ResourcePackRequest implements MinecraftPacket {
private @MonotonicNonNull String url;
@@ -35,6 +41,8 @@ public class ResourcePackRequest implements MinecraftPacket {
private boolean isRequired; // 1.17+
private @Nullable Component prompt; // 1.17+
+ private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); // 1.20.2+
+
public @Nullable String getUrl() {
return url;
}
@@ -99,6 +107,19 @@ public class ResourcePackRequest implements MinecraftPacket {
}
}
+ public VelocityResourcePackInfo toServerPromptedPack() {
+ ResourcePackInfo.Builder builder =
+ new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)).setPrompt(prompt)
+ .setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
+
+ if (hash != null && !hash.isEmpty()) {
+ if (PLAUSIBLE_SHA1_HASH.matcher(hash).matches()) {
+ builder.setHash(ByteBufUtil.decodeHexDump(hash));
+ }
+ }
+ return (VelocityResourcePackInfo) builder.build();
+ }
+
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
@@ -106,11 +127,7 @@ public class ResourcePackRequest implements MinecraftPacket {
@Override
public String toString() {
- return "ResourcePackRequest{"
- + "url='" + url + '\''
- + ", hash='" + hash + '\''
- + ", isRequired=" + isRequired
- + ", prompt='" + prompt + '\''
- + '}';
+ return "ResourcePackRequest{" + "url='" + url + '\'' + ", hash='" + hash + '\'' +
+ ", isRequired=" + isRequired + ", prompt='" + prompt + '\'' + '}';
}
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java
index 3b7c93608..4b67cf695 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java
@@ -73,9 +73,6 @@ public class ResourcePackResponse implements MinecraftPacket {
@Override
public String toString() {
- return "ResourcePackResponse{"
- + "hash=" + hash + ", "
- + "status=" + status
- + '}';
+ return "ResourcePackResponse{" + "hash=" + hash + ", " + "status=" + status + '}';
}
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java
index 11d543c89..712024f63 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java
@@ -40,6 +40,7 @@ public class Respawn implements MinecraftPacket {
private short previousGamemode; // 1.16+
private CompoundBinaryTag currentDimensionData; // 1.16.2+
private @Nullable Pair lastDeathPosition; // 1.19+
+ private int portalCooldown; // 1.20+
public Respawn() {
}
@@ -47,7 +48,7 @@ public class Respawn implements MinecraftPacket {
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, CompoundBinaryTag currentDimensionData,
- @Nullable Pair lastDeathPosition) {
+ @Nullable Pair lastDeathPosition, int portalCooldown) {
this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty;
@@ -58,13 +59,14 @@ public class Respawn implements MinecraftPacket {
this.previousGamemode = previousGamemode;
this.currentDimensionData = currentDimensionData;
this.lastDeathPosition = lastDeathPosition;
+ this.portalCooldown = portalCooldown;
}
public static Respawn fromJoinGame(JoinGame joinGame) {
return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
- joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition());
+ joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(), joinGame.getPortalCooldown());
}
public int getDimension() {
@@ -123,12 +125,20 @@ public class Respawn implements MinecraftPacket {
this.previousGamemode = previousGamemode;
}
+ public Pair getLastDeathPosition() {
+ return lastDeathPosition;
+ }
+
public void setLastDeathPosition(Pair lastDeathPosition) {
this.lastDeathPosition = lastDeathPosition;
}
- public Pair getLastDeathPosition() {
- return lastDeathPosition;
+ public int getPortalCooldown() {
+ return portalCooldown;
+ }
+
+ public void setPortalCooldown(int portalCooldown) {
+ this.portalCooldown = portalCooldown;
}
@Override
@@ -144,6 +154,7 @@ public class Respawn implements MinecraftPacket {
+ ", dimensionInfo=" + dimensionInfo
+ ", previousGamemode=" + previousGamemode
+ ", dimensionData=" + currentDimensionData
+ + ", portalCooldown=" + portalCooldown
+ '}';
}
@@ -188,6 +199,9 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
+ this.portalCooldown = ProtocolUtils.readVarInt(buf);
+ }
}
@Override
@@ -234,6 +248,10 @@ public class Respawn implements MinecraftPacket {
buf.writeBoolean(false);
}
}
+
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
+ ProtocolUtils.writeVarInt(buf, portalCooldown);
+ }
}
@Override
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java
index 0cd589ee9..57c8c61c2 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java
@@ -74,10 +74,7 @@ public class ServerLogin implements MinecraftPacket {
@Override
public String toString() {
- return "ServerLogin{"
- + "username='" + username + '\''
- + "playerKey='" + playerKey + '\''
- + '}';
+ return "ServerLogin{" + "username='" + username + '\'' + "playerKey='" + playerKey + '\'' + '}';
}
@Override
@@ -98,6 +95,11 @@ public class ServerLogin implements MinecraftPacket {
}
}
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
+ this.holderUuid = ProtocolUtils.readUuid(buf);
+ return;
+ }
+
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (buf.readBoolean()) {
holderUuid = ProtocolUtils.readUuid(buf);
@@ -125,6 +127,11 @@ public class ServerLogin implements MinecraftPacket {
}
}
+ if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
+ ProtocolUtils.writeUuid(buf, this.holderUuid);
+ return;
+ }
+
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (playerKey != null && playerKey.getSignatureHolder() != null) {
buf.writeBoolean(true);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatAcknowledgement.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatAcknowledgement.java
new file mode 100644
index 000000000..f1f9ad242
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatAcknowledgement.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet.chat;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+
+public class ChatAcknowledgement implements MinecraftPacket {
+ int offset;
+
+ public ChatAcknowledgement(int offset) {
+ this.offset = offset;
+ }
+
+ public ChatAcknowledgement() {
+ }
+
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
+ offset = ProtocolUtils.readVarInt(buf);
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
+ ProtocolUtils.writeVarInt(buf, offset);
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+
+ @Override
+ public String toString() {
+ return "ChatAcknowledgement{" +
+ "offset=" + offset +
+ '}';
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java
index fa3113af2..a1ed539d6 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java
@@ -49,4 +49,16 @@ public class LastSeenMessages {
public boolean isEmpty() {
return acknowledged.isEmpty();
}
+
+ public int getOffset() {
+ return this.offset;
+ }
+
+ @Override
+ public String toString() {
+ return "LastSeenMessages{" +
+ "offset=" + offset +
+ ", acknowledged=" + acknowledged +
+ '}';
+ }
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java
index 8aa1051aa..193371aad 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java
@@ -52,7 +52,7 @@ public class KeyedCommandHandler implements CommandHandler {
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to deny a command with signable component(s). "
+ "This is not supported. "
- + "Disconnecting player " + player.getUsername());
+ + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
@@ -75,7 +75,7 @@ public class KeyedCommandHandler implements CommandHandler {
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. "
- + "Disconnecting player " + player.getUsername());
+ + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
@@ -95,7 +95,7 @@ public class KeyedCommandHandler implements CommandHandler {
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. "
- + "Disconnecting player " + player.getUsername());
+ + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java
index 782c78357..558531b72 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java
@@ -18,8 +18,10 @@
package com.velocitypowered.proxy.protocol.packet.chat.session;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
+import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
+import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgement;
import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
@@ -47,11 +49,15 @@ public class SessionCommandHandler implements CommandHandler= 0) {
+ return CompletableFuture.completedFuture(new ChatAcknowledgement(packet.lastSeenMessages.getOffset()));
+ }
return CompletableFuture.completedFuture(null);
}
@@ -63,7 +69,7 @@ public class SessionCommandHandler implements CommandHandler= 0) {
+ return new ChatAcknowledgement(packet.lastSeenMessages.getOffset());
+ }
return null;
});
}, packet.command, packet.timeStamp);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommand.java
index 5954530e9..5ef83fa45 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommand.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommand.java
@@ -65,7 +65,8 @@ public class SessionPlayerCommand implements MinecraftPacket {
}
public boolean isSigned() {
- return salt != 0 || !lastSeenMessages.isEmpty() || !argumentSignatures.isEmpty();
+ if (salt == 0) return false;
+ return !lastSeenMessages.isEmpty() || !argumentSignatures.isEmpty();
}
@Override
@@ -73,6 +74,17 @@ public class SessionPlayerCommand implements MinecraftPacket {
return handler.handle(this);
}
+ @Override
+ public String toString() {
+ return "SessionPlayerCommand{" +
+ "command='" + command + '\'' +
+ ", timeStamp=" + timeStamp +
+ ", salt=" + salt +
+ ", argumentSignatures=" + argumentSignatures +
+ ", lastSeenMessages=" + lastSeenMessages +
+ '}';
+ }
+
public static class ArgumentSignatures {
private final List entries;
@@ -104,6 +116,12 @@ public class SessionPlayerCommand implements MinecraftPacket {
entry.encode(buf);
}
}
+ @Override
+ public String toString() {
+ return "ArgumentSignatures{" +
+ "entries=" + entries +
+ '}';
+ }
}
public static class ArgumentSignature {
@@ -120,5 +138,12 @@ public class SessionPlayerCommand implements MinecraftPacket {
ProtocolUtils.writeString(buf, name);
buf.writeBytes(signature);
}
+
+ @Override
+ public String toString() {
+ return "ArgumentSignature{" +
+ "name='" + name + '\'' +
+ '}';
+ }
}
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ActiveFeatures.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ActiveFeatures.java
new file mode 100644
index 000000000..de2a5047c
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ActiveFeatures.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet.config;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+import net.kyori.adventure.key.Key;
+
+public class ActiveFeatures implements MinecraftPacket {
+
+ private Key[] activeFeatures;
+
+ public ActiveFeatures(Key[] activeFeatures) {
+ this.activeFeatures = activeFeatures;
+ }
+
+ public ActiveFeatures() {
+ this.activeFeatures = new Key[0];
+ }
+
+ public void setActiveFeatures(Key[] activeFeatures) {
+ this.activeFeatures = activeFeatures;
+ }
+
+ public Key[] getActiveFeatures() {
+ return activeFeatures;
+ }
+
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ activeFeatures = ProtocolUtils.readKeyArray(buf);
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ ProtocolUtils.writeKeyArray(buf, activeFeatures);
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdate.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdate.java
new file mode 100644
index 000000000..866819b48
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdate.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet.config;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+
+public class FinishedUpdate implements MinecraftPacket {
+
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ }
+
+ @Override
+ public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion version) {
+ return 0;
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySync.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySync.java
new file mode 100644
index 000000000..74c275401
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySync.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet.config;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
+import io.netty.buffer.ByteBuf;
+
+public class RegistrySync extends DeferredByteBufHolder implements MinecraftPacket {
+
+ public RegistrySync() {
+ super(null);
+ }
+
+ // NBT change in 1.20.2 makes it difficult to parse this packet.
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ this.replace(buf.readRetainedSlice(buf.readableBytes()));
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ buf.writeBytes(content());
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdate.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdate.java
new file mode 100644
index 000000000..d1c25fa27
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdate.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet.config;
+
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+
+public class StartUpdate implements MinecraftPacket {
+
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ }
+
+ @Override
+ public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion version) {
+ return 0;
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdate.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdate.java
new file mode 100644
index 000000000..79b432158
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdate.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.protocol.packet.config;
+
+import com.google.common.collect.ImmutableMap;
+import com.velocitypowered.api.network.ProtocolVersion;
+import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.protocol.MinecraftPacket;
+import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+
+import java.util.Map;
+
+public class TagsUpdate implements MinecraftPacket {
+
+ private Map> tags;
+
+ public TagsUpdate(Map> tags) {
+ this.tags = tags;
+ }
+
+ public TagsUpdate() {
+ this.tags = Map.of();
+ }
+
+ @Override
+ public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ ImmutableMap.Builder> builder = ImmutableMap.builder();
+ int size = ProtocolUtils.readVarInt(buf);
+ for (int i = 0; i < size; i++) {
+ String key = ProtocolUtils.readString(buf);
+
+ int innerSize = ProtocolUtils.readVarInt(buf);
+ ImmutableMap.Builder innerBuilder = ImmutableMap.builder();
+ for (int j = 0; j < innerSize; j++) {
+ String innerKey = ProtocolUtils.readString(buf);
+ int[] innerValue = ProtocolUtils.readVarIntArray(buf);
+ innerBuilder.put(innerKey, innerValue);
+ }
+
+ builder.put(key, innerBuilder.build());
+ }
+ tags = builder.build();
+ }
+
+ @Override
+ public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
+ ProtocolVersion protocolVersion) {
+ ProtocolUtils.writeVarInt(buf, tags.size());
+ for (Map.Entry> entry : tags.entrySet()) {
+ ProtocolUtils.writeString(buf, entry.getKey());
+ // Oh, joy
+ ProtocolUtils.writeVarInt(buf, entry.getValue().size());
+ for (Map.Entry innerEntry : entry.getValue().entrySet()) {
+ // Yea, object oriented programming be damned
+ ProtocolUtils.writeString(buf, innerEntry.getKey());
+ ProtocolUtils.writeVarIntArray(buf, innerEntry.getValue());
+ }
+ }
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/provider/ComponentLoggerProviderImpl.java b/proxy/src/main/java/com/velocitypowered/proxy/provider/ComponentLoggerProviderImpl.java
new file mode 100644
index 000000000..df29ca56f
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/provider/ComponentLoggerProviderImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.provider;
+
+import com.velocitypowered.proxy.util.TranslatableMapper;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
+import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider;
+import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Velocity ComponentLogger Provider.
+ */
+@SuppressWarnings("UnstableApiUsage")
+public final class ComponentLoggerProviderImpl implements ComponentLoggerProvider {
+ private static final ANSIComponentSerializer SERIALIZER = ANSIComponentSerializer.builder()
+ .flattener(TranslatableMapper.FLATTENER)
+ .build();
+
+ @Override
+ public @NotNull ComponentLogger logger(
+ final @NotNull LoggerHelper helper,
+ final @NotNull String name
+ ) {
+ return helper.delegating(LoggerFactory.getLogger(name), SERIALIZER::serialize);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java
index 96d0b94d6..727832bd4 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java
@@ -30,9 +30,12 @@ import com.velocitypowered.api.scheduler.ScheduledTask;
import com.velocitypowered.api.scheduler.Scheduler;
import com.velocitypowered.api.scheduler.TaskStatus;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -116,17 +119,24 @@ public class VelocityScheduler implements Scheduler {
task.cancel();
}
timerExecutionService.shutdown();
- for (final PluginContainer container : this.pluginManager.getPlugins()) {
+ final List plugins = new ArrayList<>(this.pluginManager.getPlugins());
+ final Iterator pluginIterator = plugins.iterator();
+ while (pluginIterator.hasNext()) {
+ final PluginContainer container = pluginIterator.next();
if (container instanceof VelocityPluginContainer) {
- (container).getExecutorService().shutdown();
+ final VelocityPluginContainer pluginContainer = (VelocityPluginContainer) container;
+ if (pluginContainer.hasExecutorService()) {
+ container.getExecutorService().shutdown();
+ } else {
+ pluginIterator.remove();
+ }
+ } else {
+ pluginIterator.remove();
}
}
boolean allShutdown = true;
- for (final PluginContainer container : this.pluginManager.getPlugins()) {
- if (!(container instanceof VelocityPluginContainer)) {
- continue;
- }
+ for (final PluginContainer container : plugins) {
final String id = container.getDescription().getId();
final ExecutorService service = (container).getExecutorService();
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java
index 7113e06e3..c3ff9f570 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java
@@ -34,8 +34,8 @@ import java.net.SocketAddress;
import java.util.concurrent.CompletableFuture;
/**
- * Session handler used to implement
- * {@link VelocityRegisteredServer#ping(EventLoop, ProtocolVersion)}.
+ * Session handler used to implement {@link VelocityRegisteredServer#ping(EventLoop,
+ * ProtocolVersion)}.
*/
public class PingSessionHandler implements MinecraftSessionHandler {
@@ -70,6 +70,7 @@ public class PingSessionHandler implements MinecraftSessionHandler {
handshake.setProtocolVersion(version);
connection.delayedWrite(handshake);
+ connection.setActiveSessionHandler(StateRegistry.STATUS);
connection.setState(StateRegistry.STATUS);
connection.delayedWrite(StatusRequest.INSTANCE);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java
index df5e79915..728757251 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java
@@ -37,6 +37,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
+import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@@ -94,8 +95,8 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
}
/**
- * Pings the specified server using the specified event {@code loop}, claiming to be
- * {@code version}.
+ * Pings the specified server using the specified event {@code loop}, claiming to be {@code
+ * version}.
*
* @param loop the event loop to use
* @param pingOptions the options to apply to this ping
@@ -106,35 +107,30 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
throw new IllegalStateException("No Velocity proxy instance available");
}
CompletableFuture pingFuture = new CompletableFuture<>();
- server.createBootstrap(loop, serverInfo.getAddress())
- .handler(new ChannelInitializer() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline()
- .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
- .addLast(READ_TIMEOUT,
- new ReadTimeoutHandler(pingOptions.getTimeout() == 0
- ? server.getConfiguration().getReadTimeout() : pingOptions.getTimeout(),
- TimeUnit.MILLISECONDS))
- .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
- .addLast(MINECRAFT_DECODER,
- new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND))
- .addLast(MINECRAFT_ENCODER,
- new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND));
+ server.createBootstrap(loop, serverInfo.getAddress()).handler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel ch) throws Exception {
+ ch.pipeline().addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
+ .addLast(READ_TIMEOUT, new ReadTimeoutHandler(
+ pingOptions.getTimeout() == 0
+ ? server.getConfiguration().getReadTimeout()
+ : pingOptions.getTimeout(), TimeUnit.MILLISECONDS))
+ .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
+ .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND))
+ .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND));
- ch.pipeline().addLast(HANDLER, new MinecraftConnection(ch, server));
- }
- })
- .connect(serverInfo.getAddress())
- .addListener((ChannelFutureListener) future -> {
- if (future.isSuccess()) {
- MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
- conn.setSessionHandler(new PingSessionHandler(
- pingFuture, VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion()));
- } else {
- pingFuture.completeExceptionally(future.cause());
- }
- });
+ ch.pipeline().addLast(HANDLER, new MinecraftConnection(ch, server));
+ }
+ }).connect(serverInfo.getAddress()).addListener((ChannelFutureListener) future -> {
+ if (future.isSuccess()) {
+ MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
+ conn.setActiveSessionHandler(StateRegistry.HANDSHAKE,
+ new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn,
+ pingOptions.getProtocolVersion()));
+ } else {
+ pingFuture.completeExceptionally(future.cause());
+ }
+ });
return pingFuture;
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java
index f3add2290..dcf00ecae 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java
@@ -35,4 +35,6 @@ public interface InternalTabList extends TabList {
default void processRemove(RemovePlayerInfo infoPacket) {
}
+
+ void clearAllSilent();
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java
index 63a9ae56f..e24c337ed 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java
@@ -26,7 +26,6 @@ import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
-import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.ArrayList;
@@ -70,7 +69,7 @@ public class KeyedVelocityTabList implements InternalTabList {
@Override
public void clearHeaderAndFooter() {
- connection.write(HeaderAndFooter.reset());
+ this.player.clearPlayerListHeaderAndFooter();
}
@Override
@@ -131,10 +130,15 @@ public class KeyedVelocityTabList implements InternalTabList {
for (TabListEntry value : listEntries) {
items.add(LegacyPlayerListItem.Item.from(value));
}
- entries.clear();
+ clearAllSilent();
connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER, items));
}
+ @Override
+ public void clearAllSilent() {
+ entries.clear();
+ }
+
@Override
public Collection getEntries() {
return Collections.unmodifiableCollection(this.entries.values());
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java
index f707c95ca..4a95b00de 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java
@@ -25,7 +25,6 @@ import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole;
-import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
@@ -74,7 +73,7 @@ public class VelocityTabList implements InternalTabList {
@Override
public void clearHeaderAndFooter() {
- connection.write(HeaderAndFooter.reset());
+ this.player.clearPlayerListHeaderAndFooter();
}
@Override
@@ -175,6 +174,11 @@ public class VelocityTabList implements InternalTabList {
@Override
public void clearAll() {
this.connection.delayedWrite(new RemovePlayerInfo(new ArrayList<>(this.entries.keySet())));
+ clearAllSilent();
+ }
+
+ @Override
+ public void clearAllSilent() {
this.entries.clear();
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java
index d23ac4cb5..6e1777788 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java
@@ -71,6 +71,11 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList {
connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER,
Collections.singletonList(LegacyPlayerListItem.Item.from(value))));
}
+ clearAllSilent();
+ }
+
+ @Override
+ public void clearAllSilent() {
entries.clear();
nameMapping.clear();
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java
new file mode 100644
index 000000000..c2ca2366f
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.util;
+
+import java.util.Locale;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TranslatableComponent;
+import net.kyori.adventure.text.flattener.ComponentFlattener;
+import net.kyori.adventure.translation.GlobalTranslator;
+import net.kyori.adventure.translation.TranslationRegistry;
+import net.kyori.adventure.translation.Translator;
+import org.jetbrains.annotations.Nullable;
+
+
+/**
+ * Velocity Translation Mapper.
+ */
+public enum TranslatableMapper implements BiConsumer> {
+ INSTANCE;
+
+ public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder()
+ .complexMapper(TranslatableComponent.class, TranslatableMapper.INSTANCE)
+ .build();
+
+ @Override
+ public void accept(
+ final TranslatableComponent translatableComponent,
+ final Consumer componentConsumer
+ ) {
+ for (final Translator source : GlobalTranslator.translator().sources()) {
+ if (source instanceof TranslationRegistry
+ && ((TranslationRegistry) source).contains(translatableComponent.key())) {
+ componentConsumer.accept(GlobalTranslator.render(translatableComponent,
+ ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
+ return;
+ }
+ }
+ final @Nullable String fallback = translatableComponent.fallback();
+ if (fallback == null) {
+ return;
+ }
+ for (final Translator source : GlobalTranslator.translator().sources()) {
+ if (source instanceof TranslationRegistry
+ && ((TranslationRegistry) source).contains(fallback)) {
+ componentConsumer.accept(
+ GlobalTranslator.render(Component.translatable(fallback),
+ ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
+ return;
+ }
+ }
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/collect/CappedSet.java b/proxy/src/main/java/com/velocitypowered/proxy/util/collect/CappedSet.java
deleted file mode 100644
index 692910d57..000000000
--- a/proxy/src/main/java/com/velocitypowered/proxy/util/collect/CappedSet.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2019-2023 Velocity Contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.velocitypowered.proxy.util.collect;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ForwardingSet;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * An unsynchronized collection that puts an upper bound on the size of the collection.
- */
-public final class CappedSet extends ForwardingSet {
-
- private final Set delegate;
- private final int upperSize;
-
- private CappedSet(Set delegate, int upperSize) {
- this.delegate = delegate;
- this.upperSize = upperSize;
- }
-
- /**
- * Creates a capped collection backed by a {@link HashSet}.
- *
- * @param maxSize the maximum size of the collection
- * @param the type of elements in the collection
- * @return the new collection
- */
- public static Set create(int maxSize) {
- return new CappedSet<>(new HashSet<>(), maxSize);
- }
-
- @Override
- protected Set delegate() {
- return delegate;
- }
-
- @Override
- public boolean add(T element) {
- if (this.delegate.size() >= upperSize) {
- Preconditions.checkState(this.delegate.contains(element),
- "collection is too large (%s >= %s)",
- this.delegate.size(), this.upperSize);
- return false;
- }
- return this.delegate.add(element);
- }
-
- @Override
- public boolean addAll(Collection extends T> collection) {
- return this.standardAddAll(collection);
- }
-}
diff --git a/proxy/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider b/proxy/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider
new file mode 100644
index 000000000..b95209776
--- /dev/null
+++ b/proxy/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider
@@ -0,0 +1 @@
+com.velocitypowered.proxy.provider.ComponentLoggerProviderImpl
\ No newline at end of file
diff --git a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java
index 81a18e72c..05a83eea1 100644
--- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java
+++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java
@@ -44,7 +44,7 @@ class PacketRegistryTest {
private StateRegistry.PacketRegistry setupRegistry() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
- ProtocolUtils.Direction.CLIENTBOUND);
+ ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_8, null, false),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false),
@@ -84,7 +84,7 @@ class PacketRegistryTest {
@Test
void failOnNoMappings() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
- ProtocolUtils.Direction.CLIENTBOUND);
+ ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertThrows(IllegalArgumentException.class,
() -> registry.register(Handshake.class, Handshake::new));
assertThrows(IllegalArgumentException.class,
@@ -94,7 +94,7 @@ class PacketRegistryTest {
@Test
void failOnWrongOrder() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
- ProtocolUtils.Direction.CLIENTBOUND);
+ ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertThrows(IllegalArgumentException.class,
() -> registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false),
@@ -115,7 +115,7 @@ class PacketRegistryTest {
@Test
void failOnDuplicate() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
- ProtocolUtils.Direction.CLIENTBOUND);
+ ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false));
assertThrows(IllegalArgumentException.class,
@@ -129,7 +129,7 @@ class PacketRegistryTest {
@Test
void shouldNotFailWhenRegisterLatestProtocolVersion() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
- ProtocolUtils.Direction.CLIENTBOUND);
+ ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertDoesNotThrow(() -> registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false),
new StateRegistry.PacketMapping(0x01, getLast(ProtocolVersion.SUPPORTED_VERSIONS),
@@ -139,7 +139,7 @@ class PacketRegistryTest {
@Test
void registrySuppliesCorrectPacketsByProtocol() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
- ProtocolUtils.Direction.CLIENTBOUND);
+ ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, null, false),
diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/collect/CappedSetTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/collect/CappedSetTest.java
deleted file mode 100644
index 2e118b4ac..000000000
--- a/proxy/src/test/java/com/velocitypowered/proxy/util/collect/CappedSetTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2019-2021 Velocity Contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.velocitypowered.proxy.util.collect;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.Collection;
-import java.util.Set;
-import org.junit.jupiter.api.Test;
-
-class CappedSetTest {
-
- @Test
- void basicVerification() {
- Collection coll = CappedSet.create(1);
- assertTrue(coll.add("coffee"), "did not add single item");
- assertThrows(IllegalStateException.class, () -> coll.add("tea"),
- "item was added to collection although it is too full");
- assertEquals(1, coll.size(), "collection grew in size unexpectedly");
- }
-
- @Test
- void testAddAll() {
- Set doesFill1 = ImmutableSet.of("coffee", "tea");
- Set doesFill2 = ImmutableSet.of("chocolate");
- Set overfill = ImmutableSet.of("Coke", "Pepsi");
-
- Collection coll = CappedSet.create(3);
- assertTrue(coll.addAll(doesFill1), "did not add items");
- assertTrue(coll.addAll(doesFill2), "did not add items");
- assertThrows(IllegalStateException.class, () -> coll.addAll(overfill),
- "items added to collection although it is too full");
- assertEquals(3, coll.size(), "collection grew in size unexpectedly");
- }
-
- @Test
- void handlesSetBehaviorCorrectly() {
- Set doesFill1 = ImmutableSet.of("coffee", "tea");
- Set doesFill2 = ImmutableSet.of("coffee", "chocolate");
- Set overfill = ImmutableSet.of("coffee", "Coke", "Pepsi");
-
- Collection coll = CappedSet.create(3);
- assertTrue(coll.addAll(doesFill1), "did not add items");
- assertTrue(coll.addAll(doesFill2), "did not add items");
- assertThrows(IllegalStateException.class, () -> coll.addAll(overfill),
- "items added to collection although it is too full");
-
- assertFalse(coll.addAll(doesFill1), "added items?!?");
-
- assertEquals(3, coll.size(), "collection grew in size unexpectedly");
- }
-}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b8d700802..0c31266f9 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,3 +1,22 @@
+@file:Suppress("UnstableApiUsage")
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ mavenCentral()
+ maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // adventure
+ maven("https://repo.papermc.io/repository/maven-public/")
+ }
+}
+
+pluginManagement {
+ includeBuild("build-logic")
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0"
}
@@ -6,8 +25,8 @@ rootProject.name = "velocity"
sequenceOf(
"api",
- "proxy",
"native",
+ "proxy",
).forEach {
val project = ":velocity-$it"
include(project)