Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-16 21:10:30 +01:00
Merge branch 'PaperMC:dev/3.0.0' into dev/3.0.0
Dieser Commit ist enthalten in:
Commit
b3c131f96b
@ -10,6 +10,7 @@ package com.velocitypowered.api.command;
|
|||||||
import com.velocitypowered.api.event.command.CommandExecuteEvent;
|
import com.velocitypowered.api.event.command.CommandExecuteEvent;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,4 +127,15 @@ public interface CommandManager {
|
|||||||
* @return true if the alias is registered; false otherwise
|
* @return true if the alias is registered; false otherwise
|
||||||
*/
|
*/
|
||||||
boolean hasCommand(String alias);
|
boolean hasCommand(String alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given alias is registered on this manager
|
||||||
|
* and can be used by the given {@link CommandSource}.
|
||||||
|
* See {@link com.mojang.brigadier.builder.ArgumentBuilder#requires(Predicate)}
|
||||||
|
*
|
||||||
|
* @param alias the command alias to check
|
||||||
|
* @param source the command source
|
||||||
|
* @return true if the alias is registered and usable; false otherwise
|
||||||
|
*/
|
||||||
|
boolean hasCommand(String alias, CommandSource source);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* The Velocity API is licensed under the terms of the MIT License. For more details,
|
||||||
|
* reference the LICENSE file in the api top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.api.event.player;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.event.ResultedEvent;
|
||||||
|
import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is fired when a cookie response from a client is received by the proxy.
|
||||||
|
* This usually happens after either a proxy plugin or a backend server requested a cookie.
|
||||||
|
* Velocity will wait on this event to finish firing before discarding the
|
||||||
|
* received cookie (if handled) or forwarding it to the backend server.
|
||||||
|
*/
|
||||||
|
@AwaitingEvent
|
||||||
|
public final class CookieReceiveEvent implements ResultedEvent<CookieReceiveEvent.ForwardResult> {
|
||||||
|
|
||||||
|
private final Player player;
|
||||||
|
private final Key originalKey;
|
||||||
|
private final byte @Nullable [] originalData;
|
||||||
|
private ForwardResult result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param player the player who sent the cookie response
|
||||||
|
* @param key the identifier of the cookie
|
||||||
|
* @param data the data of the cookie
|
||||||
|
*/
|
||||||
|
public CookieReceiveEvent(final Player player, final Key key, final byte @Nullable [] data) {
|
||||||
|
this.player = Preconditions.checkNotNull(player, "player");
|
||||||
|
this.originalKey = Preconditions.checkNotNull(key, "key");
|
||||||
|
this.originalData = data;
|
||||||
|
this.result = ForwardResult.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ForwardResult getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setResult(ForwardResult result) {
|
||||||
|
this.result = Preconditions.checkNotNull(result, "result");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getOriginalKey() {
|
||||||
|
return originalKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte @Nullable [] getOriginalData() {
|
||||||
|
return originalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CookieReceiveEvent{"
|
||||||
|
+ ", originalKey=" + originalKey
|
||||||
|
+ ", originalData=" + Arrays.toString(originalData)
|
||||||
|
+ ", result=" + result
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result determining whether or not to forward the cookie response on.
|
||||||
|
*/
|
||||||
|
public static final class ForwardResult implements ResultedEvent.Result {
|
||||||
|
|
||||||
|
private static final ForwardResult ALLOWED = new ForwardResult(true, null, null);
|
||||||
|
private static final ForwardResult DENIED = new ForwardResult(false, null, null);
|
||||||
|
|
||||||
|
private final boolean status;
|
||||||
|
private final Key key;
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
private ForwardResult(final boolean status, final Key key, final byte[] data) {
|
||||||
|
this.status = status;
|
||||||
|
this.key = key;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowed() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return status ? "forward to backend server" : "handled by proxy";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie response to be forwarded to the backend server.
|
||||||
|
*
|
||||||
|
* @return the forward result
|
||||||
|
*/
|
||||||
|
public static ForwardResult forward() {
|
||||||
|
return ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents the cookie response from being forwarded to the backend server, the cookie response
|
||||||
|
* is handled by the proxy.
|
||||||
|
*
|
||||||
|
* @return the handled result
|
||||||
|
*/
|
||||||
|
public static ForwardResult handled() {
|
||||||
|
return DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie response to be forwarded to the backend server, but silently replaces the
|
||||||
|
* identifier of the cookie with another.
|
||||||
|
*
|
||||||
|
* @param key the identifier to use instead
|
||||||
|
* @return a result with a new key
|
||||||
|
*/
|
||||||
|
public static ForwardResult key(final Key key) {
|
||||||
|
Preconditions.checkNotNull(key, "key");
|
||||||
|
return new ForwardResult(true, key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie response to be forwarded to the backend server, but silently replaces the
|
||||||
|
* data of the cookie with another.
|
||||||
|
*
|
||||||
|
* @param data the data of the cookie to use instead
|
||||||
|
* @return a result with new data
|
||||||
|
*/
|
||||||
|
public static ForwardResult data(final byte[] data) {
|
||||||
|
return new ForwardResult(true, null, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* The Velocity API is licensed under the terms of the MIT License. For more details,
|
||||||
|
* reference the LICENSE file in the api top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.api.event.player;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.event.ResultedEvent;
|
||||||
|
import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is fired when a cookie from a client is requested either by a proxy plugin or
|
||||||
|
* by a backend server. Velocity will wait on this event to finish firing before discarding the
|
||||||
|
* cookie request (if handled) or forwarding it to the client.
|
||||||
|
*/
|
||||||
|
@AwaitingEvent
|
||||||
|
public final class CookieRequestEvent implements ResultedEvent<CookieRequestEvent.ForwardResult> {
|
||||||
|
|
||||||
|
private final Player player;
|
||||||
|
private final Key originalKey;
|
||||||
|
private ForwardResult result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param player the player from whom the cookies is requested
|
||||||
|
* @param key the identifier of the cookie
|
||||||
|
*/
|
||||||
|
public CookieRequestEvent(final Player player, final Key key) {
|
||||||
|
this.player = Preconditions.checkNotNull(player, "player");
|
||||||
|
this.originalKey = Preconditions.checkNotNull(key, "key");
|
||||||
|
this.result = ForwardResult.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ForwardResult getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setResult(ForwardResult result) {
|
||||||
|
this.result = Preconditions.checkNotNull(result, "result");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getOriginalKey() {
|
||||||
|
return originalKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CookieRequestEvent{"
|
||||||
|
+ ", originalKey=" + originalKey
|
||||||
|
+ ", result=" + result
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result determining whether or not to forward the cookie request on.
|
||||||
|
*/
|
||||||
|
public static final class ForwardResult implements Result {
|
||||||
|
|
||||||
|
private static final ForwardResult ALLOWED = new ForwardResult(true, null);
|
||||||
|
private static final ForwardResult DENIED = new ForwardResult(false, null);
|
||||||
|
|
||||||
|
private final boolean status;
|
||||||
|
private final Key key;
|
||||||
|
|
||||||
|
private ForwardResult(final boolean status, final Key key) {
|
||||||
|
this.status = status;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowed() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return status ? "forward to client" : "handled by proxy";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie request to be forwarded to the client.
|
||||||
|
*
|
||||||
|
* @return the forward result
|
||||||
|
*/
|
||||||
|
public static ForwardResult forward() {
|
||||||
|
return ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents the cookie request from being forwarded to the client, the cookie request is
|
||||||
|
* handled by the proxy.
|
||||||
|
*
|
||||||
|
* @return the handled result
|
||||||
|
*/
|
||||||
|
public static ForwardResult handled() {
|
||||||
|
return DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie request to be forwarded to the client, but silently replaces the
|
||||||
|
* identifier of the cookie with another.
|
||||||
|
*
|
||||||
|
* @param key the identifier to use instead
|
||||||
|
* @return a result with a new key
|
||||||
|
*/
|
||||||
|
public static ForwardResult key(final Key key) {
|
||||||
|
Preconditions.checkNotNull(key, "key");
|
||||||
|
return new ForwardResult(true, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* The Velocity API is licensed under the terms of the MIT License. For more details,
|
||||||
|
* reference the LICENSE file in the api top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.api.event.player;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.event.ResultedEvent;
|
||||||
|
import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is fired when a cookie should be stored on a player's client. This process can be
|
||||||
|
* initiated either by a proxy plugin or by a backend server. Velocity will wait on this event
|
||||||
|
* to finish firing before discarding the cookie (if handled) or forwarding it to the client so
|
||||||
|
* that it can store the cookie.
|
||||||
|
*/
|
||||||
|
@AwaitingEvent
|
||||||
|
public final class CookieStoreEvent implements ResultedEvent<CookieStoreEvent.ForwardResult> {
|
||||||
|
|
||||||
|
private final Player player;
|
||||||
|
private final Key originalKey;
|
||||||
|
private final byte[] originalData;
|
||||||
|
private ForwardResult result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param player the player who should store the cookie
|
||||||
|
* @param key the identifier of the cookie
|
||||||
|
* @param data the data of the cookie
|
||||||
|
*/
|
||||||
|
public CookieStoreEvent(final Player player, final Key key, final byte[] data) {
|
||||||
|
this.player = Preconditions.checkNotNull(player, "player");
|
||||||
|
this.originalKey = Preconditions.checkNotNull(key, "key");
|
||||||
|
this.originalData = Preconditions.checkNotNull(data, "data");
|
||||||
|
this.result = ForwardResult.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ForwardResult getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setResult(ForwardResult result) {
|
||||||
|
this.result = Preconditions.checkNotNull(result, "result");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getOriginalKey() {
|
||||||
|
return originalKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getOriginalData() {
|
||||||
|
return originalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CookieStoreEvent{"
|
||||||
|
+ ", originalKey=" + originalKey
|
||||||
|
+ ", originalData=" + Arrays.toString(originalData)
|
||||||
|
+ ", result=" + result
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result determining whether or not to forward the cookie on.
|
||||||
|
*/
|
||||||
|
public static final class ForwardResult implements Result {
|
||||||
|
|
||||||
|
private static final ForwardResult ALLOWED = new ForwardResult(true, null, null);
|
||||||
|
private static final ForwardResult DENIED = new ForwardResult(false, null, null);
|
||||||
|
|
||||||
|
private final boolean status;
|
||||||
|
private final Key key;
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
private ForwardResult(final boolean status, final Key key, final byte[] data) {
|
||||||
|
this.status = status;
|
||||||
|
this.key = key;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowed() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return status ? "forward to client" : "handled by proxy";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie to be forwarded to the client so that it can store it.
|
||||||
|
*
|
||||||
|
* @return the forward result
|
||||||
|
*/
|
||||||
|
public static ForwardResult forward() {
|
||||||
|
return ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents the cookie from being forwarded to the client, the cookie is handled by the proxy.
|
||||||
|
*
|
||||||
|
* @return the handled result
|
||||||
|
*/
|
||||||
|
public static ForwardResult handled() {
|
||||||
|
return DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie to be forwarded to the client so that it can store it, but silently
|
||||||
|
* replaces the identifier of the cookie with another.
|
||||||
|
*
|
||||||
|
* @param key the identifier to use instead
|
||||||
|
* @return a result with a new key
|
||||||
|
*/
|
||||||
|
public static ForwardResult key(final Key key) {
|
||||||
|
Preconditions.checkNotNull(key, "key");
|
||||||
|
return new ForwardResult(true, key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the cookie to be forwarded to the client so that it can store it, but silently
|
||||||
|
* replaces the data of the cookie with another.
|
||||||
|
*
|
||||||
|
* @param data the data of the cookie to use instead
|
||||||
|
* @return a result with new data
|
||||||
|
*/
|
||||||
|
public static ForwardResult data(final byte[] data) {
|
||||||
|
Preconditions.checkNotNull(data, "data");
|
||||||
|
return new ForwardResult(true, null, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* The Velocity API is licensed under the terms of the MIT License. For more details,
|
||||||
|
* reference the LICENSE file in the api top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.api.event.player.configuration;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.network.ProtocolState;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is executed when a player with version 1.20.2 or higher enters the configuration phase.
|
||||||
|
* <p>From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed,
|
||||||
|
* the {@linkplain Player#getProtocolState()} method is guaranteed
|
||||||
|
* to return {@link ProtocolState#CONFIGURATION}.</p>
|
||||||
|
*
|
||||||
|
* @param player The player that has entered the configuration phase.
|
||||||
|
* @param server The server that will now (re-)configure the player.
|
||||||
|
* @since 3.3.0
|
||||||
|
* @sinceMinecraft 1.20.2
|
||||||
|
*/
|
||||||
|
public record PlayerEnterConfigurationEvent(@NotNull Player player, ServerConnection server) {
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* The Velocity API is licensed under the terms of the MIT License. For more details,
|
||||||
|
* reference the LICENSE file in the api top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.api.event.player.configuration;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is executed when the player is about to finish the Configuration state.
|
||||||
|
* <p>Velocity will wait for this event to finish the configuration phase on the client.</p>
|
||||||
|
*
|
||||||
|
* @param player The player who is about to complete the configuration phase.
|
||||||
|
* @param server The server that is currently (re-)configuring the player.
|
||||||
|
* @since 3.3.0
|
||||||
|
* @sinceMinecraft 1.20.2
|
||||||
|
*/
|
||||||
|
@AwaitingEvent
|
||||||
|
public record PlayerFinishConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) {
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* The Velocity API is licensed under the terms of the MIT License. For more details,
|
||||||
|
* reference the LICENSE file in the api top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.api.event.player.configuration;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.network.ProtocolState;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event executed when a player of version 1.20.2 or higher finishes the Configuration state.
|
||||||
|
* <p>From this moment on, the {@link Player#getProtocolState()} method
|
||||||
|
* will return {@link ProtocolState#PLAY}.</p>
|
||||||
|
*
|
||||||
|
* @param player The player who has completed the Configuration state
|
||||||
|
* @param server The server that has (re-)configured the player.
|
||||||
|
* @since 3.3.0
|
||||||
|
* @sinceMinecraft 1.20.2
|
||||||
|
*/
|
||||||
|
public record PlayerFinishedConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) {
|
||||||
|
}
|
@ -8,9 +8,11 @@
|
|||||||
package com.velocitypowered.api.event.proxy;
|
package com.velocitypowered.api.event.proxy;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.event.ResultedEvent;
|
||||||
import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
||||||
import com.velocitypowered.api.proxy.InboundConnection;
|
import com.velocitypowered.api.proxy.InboundConnection;
|
||||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is fired when a request for server information is sent by a remote client, or when the
|
* This event is fired when a request for server information is sent by a remote client, or when the
|
||||||
@ -20,28 +22,71 @@ import com.velocitypowered.api.proxy.server.ServerPing;
|
|||||||
* amount of ping packets a client can send.
|
* amount of ping packets a client can send.
|
||||||
*/
|
*/
|
||||||
@AwaitingEvent
|
@AwaitingEvent
|
||||||
public final class ProxyPingEvent {
|
public final class ProxyPingEvent implements ResultedEvent<ResultedEvent.GenericResult> {
|
||||||
|
|
||||||
private final InboundConnection connection;
|
private final InboundConnection connection;
|
||||||
private ServerPing ping;
|
private ServerPing ping;
|
||||||
|
private GenericResult result = GenericResult.allowed();
|
||||||
|
|
||||||
public ProxyPingEvent(InboundConnection connection, ServerPing ping) {
|
public ProxyPingEvent(final InboundConnection connection, final ServerPing ping) {
|
||||||
this.connection = Preconditions.checkNotNull(connection, "connection");
|
this.connection = Preconditions.checkNotNull(connection, "connection");
|
||||||
this.ping = Preconditions.checkNotNull(ping, "ping");
|
this.ping = Preconditions.checkNotNull(ping, "ping");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the connection to which the corresponding ServerPing will be sent.
|
||||||
|
*
|
||||||
|
* @return the connection that has sent the ServerPing request
|
||||||
|
*/
|
||||||
public InboundConnection getConnection() {
|
public InboundConnection getConnection() {
|
||||||
return connection;
|
return this.connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ServerPing to send to the connection.
|
||||||
|
*
|
||||||
|
* @return the ServerPing to send
|
||||||
|
*/
|
||||||
public ServerPing getPing() {
|
public ServerPing getPing() {
|
||||||
return ping;
|
return this.ping;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPing(ServerPing ping) {
|
/**
|
||||||
|
* Sets the ServerPing to send to the connection.
|
||||||
|
*
|
||||||
|
* @param ping sets the ServerPing to send
|
||||||
|
*/
|
||||||
|
public void setPing(final @NotNull ServerPing ping) {
|
||||||
this.ping = Preconditions.checkNotNull(ping, "ping");
|
this.ping = Preconditions.checkNotNull(ping, "ping");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether to avoid sending a ping response to the connection.
|
||||||
|
*
|
||||||
|
* @return if a ping response to the connection will be avoided
|
||||||
|
* @apiNote For the ProxyPingEvent executed to obtain the MOTD for the ServerData
|
||||||
|
* sent to players of versions higher than 1.19.1,
|
||||||
|
* the cancellation of this event will have no effect.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public GenericResult getResult() {
|
||||||
|
return this.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to avoid sending a ping response to the connection.
|
||||||
|
* This will automatically close the connection.
|
||||||
|
*
|
||||||
|
* @param result if a ping response to the connection will be avoided
|
||||||
|
* @apiNote For the ProxyPingEvent executed to obtain the MOTD for the ServerData
|
||||||
|
* sent to players of versions higher than 1.19.1,
|
||||||
|
* the cancellation of this event will have no effect.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setResult(final @NotNull GenericResult result) {
|
||||||
|
this.result = Preconditions.checkNotNull(result, "result");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ProxyPingEvent{"
|
return "ProxyPingEvent{"
|
||||||
|
@ -86,7 +86,8 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
|
|||||||
MINECRAFT_1_20(763, "1.20", "1.20.1"),
|
MINECRAFT_1_20(763, "1.20", "1.20.1"),
|
||||||
MINECRAFT_1_20_2(764, "1.20.2"),
|
MINECRAFT_1_20_2(764, "1.20.2"),
|
||||||
MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"),
|
MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"),
|
||||||
MINECRAFT_1_20_5(766, "1.20.5", "1.20.6");
|
MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"),
|
||||||
|
MINECRAFT_1_21(767, "1.21");
|
||||||
|
|
||||||
private static final int SNAPSHOT_BIT = 30;
|
private static final int SNAPSHOT_BIT = 30;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
package com.velocitypowered.api.proxy;
|
package com.velocitypowered.api.proxy;
|
||||||
|
|
||||||
import com.velocitypowered.api.command.CommandSource;
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.event.player.CookieReceiveEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||||
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
|
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
@ -436,4 +437,28 @@ public interface Player extends
|
|||||||
* @since 3.3.0
|
* @since 3.3.0
|
||||||
*/
|
*/
|
||||||
void transferToHost(@NotNull InetSocketAddress address);
|
void transferToHost(@NotNull InetSocketAddress address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a cookie with arbitrary data on the player's client.
|
||||||
|
*
|
||||||
|
* @param key the identifier of the cookie
|
||||||
|
* @param data the data of the cookie
|
||||||
|
* @throws IllegalArgumentException if the player is from a version lower than 1.20.5
|
||||||
|
* @since 3.3.0
|
||||||
|
* @sinceMinecraft 1.20.5
|
||||||
|
*/
|
||||||
|
void storeCookie(Key key, byte[] data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a previously stored cookie from the player's client.
|
||||||
|
* Calling this method causes the client to send the cookie to the proxy.
|
||||||
|
* To retrieve the actual data of the requested cookie, you have to use the
|
||||||
|
* {@link CookieReceiveEvent}.
|
||||||
|
*
|
||||||
|
* @param key the identifier of the cookie
|
||||||
|
* @throws IllegalArgumentException if the player is from a version lower than 1.20.5
|
||||||
|
* @since 3.3.0
|
||||||
|
* @sinceMinecraft 1.20.5
|
||||||
|
*/
|
||||||
|
void requestCookie(Key key);
|
||||||
}
|
}
|
@ -45,7 +45,7 @@ import com.velocitypowered.proxy.command.builtin.ShutdownCommand;
|
|||||||
import com.velocitypowered.proxy.command.builtin.VelocityCommand;
|
import com.velocitypowered.proxy.command.builtin.VelocityCommand;
|
||||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.connection.util.ServerListPingHandler;
|
import com.velocitypowered.proxy.connection.util.ServerListPingHandler;
|
||||||
import com.velocitypowered.proxy.console.VelocityConsole;
|
import com.velocitypowered.proxy.console.VelocityConsole;
|
||||||
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
||||||
@ -469,11 +469,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
boolean queryPortChanged = newConfiguration.getQueryPort() != configuration.getQueryPort();
|
boolean queryPortChanged = newConfiguration.getQueryPort() != configuration.getQueryPort();
|
||||||
boolean queryAlreadyEnabled = configuration.isQueryEnabled();
|
boolean queryAlreadyEnabled = configuration.isQueryEnabled();
|
||||||
boolean queryEnabled = newConfiguration.isQueryEnabled();
|
boolean queryEnabled = newConfiguration.isQueryEnabled();
|
||||||
if ((!queryEnabled && queryAlreadyEnabled) || queryPortChanged) {
|
if (queryAlreadyEnabled && (!queryEnabled || queryPortChanged)) {
|
||||||
this.cm.close(new InetSocketAddress(
|
this.cm.close(new InetSocketAddress(
|
||||||
configuration.getBind().getHostString(), configuration.getQueryPort()));
|
configuration.getBind().getHostString(), configuration.getQueryPort()));
|
||||||
}
|
}
|
||||||
if (queryEnabled && queryPortChanged) {
|
if (queryEnabled && (!queryAlreadyEnabled || queryPortChanged)) {
|
||||||
this.cm.queryBind(newConfiguration.getBind().getHostString(),
|
this.cm.queryBind(newConfiguration.getBind().getHostString(),
|
||||||
newConfiguration.getQueryPort());
|
newConfiguration.getQueryPort());
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ import com.mojang.brigadier.tree.CommandNode;
|
|||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
import com.mojang.brigadier.tree.RootCommandNode;
|
import com.mojang.brigadier.tree.RootCommandNode;
|
||||||
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode;
|
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import org.checkerframework.checker.lock.qual.GuardedBy;
|
import org.checkerframework.checker.lock.qual.GuardedBy;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
@ -66,6 +68,7 @@ public final class CommandGraphInjector<S> {
|
|||||||
public void inject(final RootCommandNode<S> dest, final S source) {
|
public void inject(final RootCommandNode<S> dest, final S source) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
final Map<CommandNode<S>, CommandNode<S>> done = new IdentityHashMap<>();
|
||||||
final RootCommandNode<S> origin = this.dispatcher.getRoot();
|
final RootCommandNode<S> origin = this.dispatcher.getRoot();
|
||||||
final CommandContextBuilder<S> rootContext =
|
final CommandContextBuilder<S> rootContext =
|
||||||
new CommandContextBuilder<>(this.dispatcher, source, origin, 0);
|
new CommandContextBuilder<>(this.dispatcher, source, origin, 0);
|
||||||
@ -88,7 +91,7 @@ public final class CommandGraphInjector<S> {
|
|||||||
VelocityCommands.getArgumentsNode(asLiteral);
|
VelocityCommands.getArgumentsNode(asLiteral);
|
||||||
if (argsNode == null) {
|
if (argsNode == null) {
|
||||||
// This literal is associated to a BrigadierCommand, filter normally.
|
// This literal is associated to a BrigadierCommand, filter normally.
|
||||||
this.copyChildren(node, copy, source);
|
this.copyChildren(node, copy, source, done);
|
||||||
} else {
|
} else {
|
||||||
// Copy all children nodes (arguments node and hints)
|
// Copy all children nodes (arguments node and hints)
|
||||||
for (final CommandNode<S> child : node.getChildren()) {
|
for (final CommandNode<S> child : node.getChildren()) {
|
||||||
@ -102,7 +105,10 @@ public final class CommandGraphInjector<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable CommandNode<S> filterNode(final CommandNode<S> node, final S source) {
|
private @Nullable CommandNode<S> filterNode(final CommandNode<S> node, final S source, final Map<CommandNode<S>, CommandNode<S>> done) {
|
||||||
|
if (done.containsKey(node)) {
|
||||||
|
return done.get(node);
|
||||||
|
}
|
||||||
// We only check the non-context requirement when filtering alias nodes.
|
// We only check the non-context requirement when filtering alias nodes.
|
||||||
// Otherwise, we would need to manually craft context builder and reader instances,
|
// Otherwise, we would need to manually craft context builder and reader instances,
|
||||||
// which is both incorrect and inefficient. The reason why we can do so for alias
|
// which is both incorrect and inefficient. The reason why we can do so for alias
|
||||||
@ -116,18 +122,18 @@ public final class CommandGraphInjector<S> {
|
|||||||
// Redirects to non-Brigadier commands are not supported. Luckily,
|
// Redirects to non-Brigadier commands are not supported. Luckily,
|
||||||
// we don't expose the root node to API users, so they can't access
|
// we don't expose the root node to API users, so they can't access
|
||||||
// nodes associated to other commands.
|
// nodes associated to other commands.
|
||||||
final CommandNode<S> target = this.filterNode(node.getRedirect(), source);
|
final CommandNode<S> target = this.filterNode(node.getRedirect(), source, done);
|
||||||
builder.forward(target, builder.getRedirectModifier(), builder.isFork());
|
builder.forward(target, builder.getRedirectModifier(), builder.isFork());
|
||||||
}
|
}
|
||||||
final CommandNode<S> result = builder.build();
|
final CommandNode<S> result = builder.build();
|
||||||
this.copyChildren(node, result, source);
|
done.put(node, result);
|
||||||
|
this.copyChildren(node, result, source, done);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyChildren(final CommandNode<S> parent, final CommandNode<S> dest,
|
private void copyChildren(final CommandNode<S> parent, final CommandNode<S> dest, final S source, final Map<CommandNode<S>, CommandNode<S>> done) {
|
||||||
final S source) {
|
|
||||||
for (final CommandNode<S> child : parent.getChildren()) {
|
for (final CommandNode<S> child : parent.getChildren()) {
|
||||||
final CommandNode<S> filtered = this.filterNode(child, source);
|
final CommandNode<S> filtered = this.filterNode(child, source, done);
|
||||||
if (filtered != null) {
|
if (filtered != null) {
|
||||||
dest.addChild(filtered);
|
dest.addChild(filtered);
|
||||||
}
|
}
|
||||||
|
@ -347,8 +347,19 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasCommand(final String alias) {
|
public boolean hasCommand(final String alias) {
|
||||||
|
return getCommand(alias) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasCommand(String alias, CommandSource source) {
|
||||||
|
Preconditions.checkNotNull(source, "source");
|
||||||
|
CommandNode<CommandSource> command = getCommand(alias);
|
||||||
|
return command != null && command.canUse(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandNode<CommandSource> getCommand(final String alias) {
|
||||||
Preconditions.checkNotNull(alias, "alias");
|
Preconditions.checkNotNull(alias, "alias");
|
||||||
return dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH)) != null;
|
return dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting // this constitutes unsafe publication
|
@VisibleForTesting // this constitutes unsafe publication
|
||||||
|
@ -74,7 +74,7 @@ public final class VelocityCommand {
|
|||||||
@SuppressWarnings("checkstyle:MissingJavadocMethod")
|
@SuppressWarnings("checkstyle:MissingJavadocMethod")
|
||||||
public static BrigadierCommand create(final VelocityServer server) {
|
public static BrigadierCommand create(final VelocityServer server) {
|
||||||
final LiteralCommandNode<CommandSource> dump = BrigadierCommand.literalArgumentBuilder("dump")
|
final LiteralCommandNode<CommandSource> dump = BrigadierCommand.literalArgumentBuilder("dump")
|
||||||
.requires(source -> source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE)
|
.requires(source -> source.getPermissionValue("velocity.command.dump") == Tristate.TRUE)
|
||||||
.executes(new Dump(server))
|
.executes(new Dump(server))
|
||||||
.build();
|
.build();
|
||||||
final LiteralCommandNode<CommandSource> heap = BrigadierCommand.literalArgumentBuilder("heap")
|
final LiteralCommandNode<CommandSource> heap = BrigadierCommand.literalArgumentBuilder("heap")
|
||||||
|
@ -47,7 +47,8 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEnco
|
|||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||||
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueHandler;
|
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler;
|
||||||
|
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler;
|
||||||
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
@ -148,13 +149,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg instanceof MinecraftPacket) {
|
if (msg instanceof MinecraftPacket pkt) {
|
||||||
MinecraftPacket pkt = (MinecraftPacket) msg;
|
|
||||||
if (!pkt.handle(activeSessionHandler)) {
|
if (!pkt.handle(activeSessionHandler)) {
|
||||||
activeSessionHandler.handleGeneric((MinecraftPacket) msg);
|
activeSessionHandler.handleGeneric((MinecraftPacket) msg);
|
||||||
}
|
}
|
||||||
} else if (msg instanceof HAProxyMessage) {
|
} else if (msg instanceof HAProxyMessage proxyMessage) {
|
||||||
HAProxyMessage proxyMessage = (HAProxyMessage) msg;
|
|
||||||
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
|
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
|
||||||
proxyMessage.sourcePort());
|
proxyMessage.sourcePort());
|
||||||
} else if (msg instanceof ByteBuf) {
|
} else if (msg instanceof ByteBuf) {
|
||||||
@ -383,9 +382,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
if (state == StateRegistry.CONFIG) {
|
if (state == StateRegistry.CONFIG) {
|
||||||
// Activate the play packet queue
|
// Activate the play packet queue
|
||||||
addPlayPacketQueueHandler();
|
addPlayPacketQueueHandler();
|
||||||
} else if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) != null) {
|
} else {
|
||||||
// Remove the queue
|
// Remove the queue
|
||||||
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE);
|
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) != null) {
|
||||||
|
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_OUTBOUND);
|
||||||
|
}
|
||||||
|
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) != null) {
|
||||||
|
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_INBOUND);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,10 +397,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
* Adds the play packet queue handler.
|
* Adds the play packet queue handler.
|
||||||
*/
|
*/
|
||||||
public void addPlayPacketQueueHandler() {
|
public void addPlayPacketQueueHandler() {
|
||||||
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) == null) {
|
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) == null) {
|
||||||
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE,
|
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE_OUTBOUND,
|
||||||
new PlayPacketQueueHandler(this.protocolVersion,
|
new PlayPacketQueueOutboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftEncoder.class).getDirection()));
|
||||||
channel.pipeline().get(MinecraftEncoder.class).getDirection()));
|
}
|
||||||
|
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) == null) {
|
||||||
|
this.channel.pipeline().addAfter(Connections.MINECRAFT_DECODER, Connections.PLAY_PACKET_QUEUE_INBOUND,
|
||||||
|
new PlayPacketQueueInboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftDecoder.class).getDirection()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
|
||||||
@ -45,6 +47,7 @@ import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
|
import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
|
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
|
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
|
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
|
||||||
@ -62,6 +65,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket;
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket;
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
|
||||||
@ -339,4 +344,24 @@ public interface MinecraftSessionHandler {
|
|||||||
default boolean handle(KnownPacksPacket packet) {
|
default boolean handle(KnownPacksPacket packet) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean handle(ClientboundStoreCookiePacket packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean handle(ClientboundCookieRequestPacket packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean handle(ServerboundCookieResponsePacket packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean handle(ClientboundCustomReportDetailsPacket packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean handle(ClientboundServerLinksPacket packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ import com.velocitypowered.api.command.CommandSource;
|
|||||||
import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent;
|
import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent;
|
||||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
import com.velocitypowered.api.event.connection.PreTransferEvent;
|
import com.velocitypowered.api.event.connection.PreTransferEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieRequestEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieStoreEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||||
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
||||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||||
@ -38,8 +40,8 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
|
import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
@ -48,6 +50,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
|
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
|
||||||
@ -70,6 +74,7 @@ import io.netty.channel.Channel;
|
|||||||
import io.netty.handler.timeout.ReadTimeoutException;
|
import io.netty.handler.timeout.ReadTimeoutException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@ -390,6 +395,39 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundStoreCookiePacket packet) {
|
||||||
|
server.getEventManager()
|
||||||
|
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
final byte[] resultedData = event.getResult().getData() == null
|
||||||
|
? event.getOriginalData() : event.getResult().getData();
|
||||||
|
|
||||||
|
playerConnection.write(new ClientboundStoreCookiePacket(resultedKey, resultedData));
|
||||||
|
}
|
||||||
|
}, playerConnection.eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundCookieRequestPacket packet) {
|
||||||
|
server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
|
||||||
|
playerConnection.write(new ClientboundCookieRequestPacket(resultedKey));
|
||||||
|
}
|
||||||
|
}, playerConnection.eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleGeneric(MinecraftPacket packet) {
|
public void handleGeneric(MinecraftPacket packet) {
|
||||||
if (packet instanceof PluginMessagePacket) {
|
if (packet instanceof PluginMessagePacket) {
|
||||||
|
@ -37,7 +37,6 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
import net.kyori.adventure.identity.Identity;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.serializer.ComponentSerializer;
|
import net.kyori.adventure.text.serializer.ComponentSerializer;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
@ -143,7 +142,7 @@ public class BungeeCordMessageResponder {
|
|||||||
out.writeUTF("PlayerList");
|
out.writeUTF("PlayerList");
|
||||||
out.writeUTF(info.getServerInfo().getName());
|
out.writeUTF(info.getServerInfo().getName());
|
||||||
|
|
||||||
StringJoiner joiner = new StringJoiner(", ");
|
final StringJoiner joiner = new StringJoiner(", ");
|
||||||
for (Player online : info.getPlayersConnected()) {
|
for (Player online : info.getPlayersConnected()) {
|
||||||
joiner.add(online.getUsername());
|
joiner.add(online.getUsername());
|
||||||
}
|
}
|
||||||
@ -187,10 +186,9 @@ public class BungeeCordMessageResponder {
|
|||||||
|
|
||||||
Component messageComponent = serializer.deserialize(message);
|
Component messageComponent = serializer.deserialize(message);
|
||||||
if (target.equals("ALL")) {
|
if (target.equals("ALL")) {
|
||||||
proxy.sendMessage(Identity.nil(), messageComponent);
|
proxy.sendMessage(messageComponent);
|
||||||
} else {
|
} else {
|
||||||
proxy.getPlayer(target).ifPresent(player -> player.sendMessage(Identity.nil(),
|
proxy.getPlayer(target).ifPresent(player -> player.sendMessage(messageComponent));
|
||||||
messageComponent));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
package com.velocitypowered.proxy.connection.backend;
|
package com.velocitypowered.proxy.connection.backend;
|
||||||
|
|
||||||
import com.velocitypowered.api.event.connection.PreTransferEvent;
|
import com.velocitypowered.api.event.connection.PreTransferEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieRequestEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieStoreEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||||
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
@ -27,19 +29,23 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler;
|
import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TransferPacket;
|
import com.velocitypowered.proxy.protocol.packet.TransferPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
|
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
|
||||||
@ -48,6 +54,7 @@ import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@ -111,9 +118,22 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundCustomReportDetailsPacket packet) {
|
||||||
|
serverConn.getPlayer().getConnection().write(packet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundServerLinksPacket packet) {
|
||||||
|
serverConn.getPlayer().getConnection().write(packet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(KeepAlivePacket packet) {
|
public boolean handle(KeepAlivePacket packet) {
|
||||||
serverConn.ensureConnected().write(packet);
|
serverConn.getPendingPings().put(packet.getRandomId(), System.nanoTime());
|
||||||
|
serverConn.getPlayer().getConnection().write(packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,30 +194,25 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(FinishedUpdatePacket packet) {
|
public boolean handle(FinishedUpdatePacket packet) {
|
||||||
MinecraftConnection smc = serverConn.ensureConnected();
|
final MinecraftConnection smc = serverConn.ensureConnected();
|
||||||
ConnectedPlayer player = serverConn.getPlayer();
|
final ConnectedPlayer player = serverConn.getPlayer();
|
||||||
ClientConfigSessionHandler configHandler =
|
final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
|
||||||
(ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
|
|
||||||
|
|
||||||
smc.setAutoReading(false);
|
|
||||||
// Even when not auto reading messages are still decoded. Decode them with the correct state
|
|
||||||
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
|
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
|
||||||
configHandler.handleBackendFinishUpdate(serverConn).thenAcceptAsync((unused) -> {
|
//noinspection DataFlowIssue
|
||||||
|
configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> {
|
||||||
|
smc.write(FinishedUpdatePacket.INSTANCE);
|
||||||
if (serverConn == player.getConnectedServer()) {
|
if (serverConn == player.getConnectedServer()) {
|
||||||
smc.setActiveSessionHandler(StateRegistry.PLAY);
|
smc.setActiveSessionHandler(StateRegistry.PLAY);
|
||||||
player.sendPlayerListHeaderAndFooter(
|
player.sendPlayerListHeaderAndFooter(player.getPlayerListHeader(), player.getPlayerListFooter());
|
||||||
player.getPlayerListHeader(), player.getPlayerListFooter());
|
|
||||||
// The client cleared the tab list. TODO: Restore changes done via TabList API
|
// The client cleared the tab list. TODO: Restore changes done via TabList API
|
||||||
player.getTabList().clearAllSilent();
|
player.getTabList().clearAllSilent();
|
||||||
} else {
|
} else {
|
||||||
smc.setActiveSessionHandler(StateRegistry.PLAY,
|
smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture));
|
||||||
new TransitionSessionHandler(server, serverConn, resultFuture));
|
|
||||||
}
|
}
|
||||||
if (player.resourcePackHandler().getFirstAppliedPack() == null
|
if (player.resourcePackHandler().getFirstAppliedPack() == null && resourcePackToApply != null) {
|
||||||
&& resourcePackToApply != null) {
|
|
||||||
player.resourcePackHandler().queueResourcePack(resourcePackToApply);
|
player.resourcePackHandler().queueResourcePack(resourcePackToApply);
|
||||||
}
|
}
|
||||||
smc.setAutoReading(true);
|
|
||||||
}, smc.eventLoop());
|
}, smc.eventLoop());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -251,6 +266,40 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundStoreCookiePacket packet) {
|
||||||
|
server.getEventManager()
|
||||||
|
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
final byte[] resultedData = event.getResult().getData() == null
|
||||||
|
? event.getOriginalData() : event.getResult().getData();
|
||||||
|
|
||||||
|
serverConn.getPlayer().getConnection()
|
||||||
|
.write(new ClientboundStoreCookiePacket(resultedKey, resultedData));
|
||||||
|
}
|
||||||
|
}, serverConn.ensureConnected().eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundCookieRequestPacket packet) {
|
||||||
|
server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
|
||||||
|
serverConn.getPlayer().getConnection().write(new ClientboundCookieRequestPacket(resultedKey));
|
||||||
|
}
|
||||||
|
}, serverConn.ensureConnected().eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnected() {
|
public void disconnected() {
|
||||||
resultFuture.completeExceptionally(
|
resultFuture.completeExceptionally(
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.connection.backend;
|
package com.velocitypowered.proxy.connection.backend;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.event.player.CookieRequestEvent;
|
||||||
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
|
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
@ -31,6 +32,8 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
|||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket;
|
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket;
|
||||||
@ -43,6 +46,7 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -162,18 +166,35 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
if (player.getClientSettingsPacket() != null) {
|
if (player.getClientSettingsPacket() != null) {
|
||||||
smc.write(player.getClientSettingsPacket());
|
smc.write(player.getClientSettingsPacket());
|
||||||
}
|
}
|
||||||
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler) {
|
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) {
|
||||||
smc.setAutoReading(false);
|
smc.setAutoReading(false);
|
||||||
((ClientPlaySessionHandler) player.getConnection()
|
clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop());
|
||||||
.getActiveSessionHandler()).doSwitch().thenAcceptAsync((unused) -> {
|
|
||||||
smc.setAutoReading(true);
|
|
||||||
}, smc.eventLoop());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundStoreCookiePacket packet) {
|
||||||
|
throw new IllegalStateException("Can only store cookie in CONFIGURATION or PLAY protocol");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ClientboundCookieRequestPacket packet) {
|
||||||
|
server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
|
||||||
|
serverConn.getPlayer().getConnection().write(new ClientboundCookieRequestPacket(resultedKey));
|
||||||
|
}
|
||||||
|
}, serverConn.ensureConnected().eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exception(Throwable throwable) {
|
public void exception(Throwable throwable) {
|
||||||
resultFuture.completeExceptionally(throwable);
|
resultFuture.completeExceptionally(throwable);
|
||||||
|
@ -24,6 +24,7 @@ import com.velocitypowered.api.event.connection.DisconnectEvent;
|
|||||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieReceiveEvent;
|
||||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
|
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
@ -41,6 +42,7 @@ import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
|
|||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket;
|
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
|
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -188,6 +190,24 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ServerboundCookieResponsePacket packet) {
|
||||||
|
server.getEventManager()
|
||||||
|
.fire(new CookieReceiveEvent(connectedPlayer, packet.getKey(), packet.getPayload()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
// The received cookie must have been requested by a proxy plugin in login phase,
|
||||||
|
// because if a backend server requests a cookie in login phase, the client is already
|
||||||
|
// in config phase. Therefore, the only way, we receive a CookieResponsePacket from a
|
||||||
|
// client in login phase is when a proxy plugin requested a cookie in login phase.
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"A cookie was requested by a proxy plugin in login phase but the response wasn't handled");
|
||||||
|
}
|
||||||
|
}, mcConnection.eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
|
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
|
||||||
mcConnection.setAssociation(player);
|
mcConnection.setAssociation(player);
|
||||||
|
|
||||||
|
@ -17,7 +17,10 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.event.player.CookieReceiveEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
||||||
|
import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent;
|
||||||
|
import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
@ -32,6 +35,7 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket;
|
import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
|
||||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||||
@ -39,6 +43,7 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -128,16 +133,41 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
|||||||
public boolean handle(PingIdentifyPacket packet) {
|
public boolean handle(PingIdentifyPacket packet) {
|
||||||
if (player.getConnectionInFlight() != null) {
|
if (player.getConnectionInFlight() != null) {
|
||||||
player.getConnectionInFlight().ensureConnected().write(packet);
|
player.getConnectionInFlight().ensureConnected().write(packet);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(KnownPacksPacket packet) {
|
public boolean handle(KnownPacksPacket packet) {
|
||||||
if (player.getConnectionInFlight() != null) {
|
if (player.getConnectionInFlight() != null) {
|
||||||
player.getConnectionInFlight().ensureConnected().write(packet);
|
player.getConnectionInFlight().ensureConnected().write(packet);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ServerboundCookieResponsePacket packet) {
|
||||||
|
server.getEventManager()
|
||||||
|
.fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final VelocityServerConnection serverConnection = player.getConnectionInFlight();
|
||||||
|
if (serverConnection != null) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
final byte[] resultedData = event.getResult().getData() == null
|
||||||
|
? event.getOriginalData() : event.getResult().getData();
|
||||||
|
|
||||||
|
serverConnection.ensureConnected()
|
||||||
|
.write(new ServerboundCookieResponsePacket(resultedKey, resultedData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, player.getConnection().eventLoop());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,13 +248,11 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
|||||||
smc.write(brandPacket);
|
smc.write(brandPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
player.getConnection().eventLoop().execute(() -> {
|
server.getEventManager().fire(new PlayerFinishConfigurationEvent(player, serverConn)).thenAcceptAsync(event -> {
|
||||||
player.getConnection().write(FinishedUpdatePacket.INSTANCE);
|
player.getConnection().write(FinishedUpdatePacket.INSTANCE);
|
||||||
player.getConnection().getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
|
player.getConnection().getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
|
||||||
});
|
server.getEventManager().fireAndForget(new PlayerFinishedConfigurationEvent(player, serverConn));
|
||||||
|
}, player.getConnection().eventLoop());
|
||||||
smc.write(FinishedUpdatePacket.INSTANCE);
|
|
||||||
smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
|
|
||||||
|
|
||||||
return configSwitchFuture;
|
return configSwitchFuture;
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,11 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.mojang.brigadier.suggestion.Suggestion;
|
import com.mojang.brigadier.suggestion.Suggestion;
|
||||||
import com.velocitypowered.api.command.VelocityBrigadierMessage;
|
import com.velocitypowered.api.command.VelocityBrigadierMessage;
|
||||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieReceiveEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
|
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
||||||
import com.velocitypowered.api.event.player.TabCompleteEvent;
|
import com.velocitypowered.api.event.player.TabCompleteEvent;
|
||||||
|
import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||||
@ -48,6 +50,7 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
|
import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket.Offer;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket.Offer;
|
||||||
@ -83,6 +86,7 @@ import java.util.UUID;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -403,6 +407,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
// Complete client switch
|
// Complete client switch
|
||||||
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
|
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
|
||||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||||
|
server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(player, serverConnection));
|
||||||
if (serverConnection != null) {
|
if (serverConnection != null) {
|
||||||
MinecraftConnection smc = serverConnection.ensureConnected();
|
MinecraftConnection smc = serverConnection.ensureConnected();
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
@ -418,6 +423,28 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ServerboundCookieResponsePacket packet) {
|
||||||
|
server.getEventManager()
|
||||||
|
.fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload()))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||||
|
if (serverConnection != null) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
final byte[] resultedData = event.getResult().getData() == null
|
||||||
|
? event.getOriginalData() : event.getResult().getData();
|
||||||
|
|
||||||
|
serverConnection.ensureConnected()
|
||||||
|
.write(new ServerboundCookieResponsePacket(resultedKey, resultedData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, player.getConnection().eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleGeneric(MinecraftPacket packet) {
|
public void handleGeneric(MinecraftPacket packet) {
|
||||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||||
@ -487,7 +514,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
* @return a future that completes when the switch is complete
|
* @return a future that completes when the switch is complete
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Void> doSwitch() {
|
public CompletableFuture<Void> doSwitch() {
|
||||||
VelocityServerConnection existingConnection = player.getConnectedServer();
|
final VelocityServerConnection existingConnection = player.getConnectedServer();
|
||||||
|
|
||||||
if (existingConnection != null) {
|
if (existingConnection != null) {
|
||||||
// Shut down the existing server connection.
|
// Shut down the existing server connection.
|
||||||
@ -630,7 +657,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String commandLabel = command.substring(0, commandEndPosition);
|
String commandLabel = command.substring(0, commandEndPosition);
|
||||||
if (!server.getCommandManager().hasCommand(commandLabel)) {
|
if (!server.getCommandManager().hasCommand(commandLabel, player)) {
|
||||||
if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) {
|
if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) {
|
||||||
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide
|
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide
|
||||||
// additional tab completion support.
|
// additional tab completion support.
|
||||||
|
@ -27,6 +27,8 @@ import com.google.gson.JsonObject;
|
|||||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||||
import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus;
|
import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus;
|
||||||
import com.velocitypowered.api.event.connection.PreTransferEvent;
|
import com.velocitypowered.api.event.connection.PreTransferEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieRequestEvent;
|
||||||
|
import com.velocitypowered.api.event.player.CookieStoreEvent;
|
||||||
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||||
import com.velocitypowered.api.event.player.KickedFromServerEvent.DisconnectPlayer;
|
import com.velocitypowered.api.event.player.KickedFromServerEvent.DisconnectPlayer;
|
||||||
import com.velocitypowered.api.event.player.KickedFromServerEvent.Notify;
|
import com.velocitypowered.api.event.player.KickedFromServerEvent.Notify;
|
||||||
@ -35,6 +37,7 @@ import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResu
|
|||||||
import com.velocitypowered.api.event.player.PlayerModInfoEvent;
|
import com.velocitypowered.api.event.player.PlayerModInfoEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
||||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||||
|
import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolState;
|
import com.velocitypowered.api.network.ProtocolState;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.permission.PermissionFunction;
|
import com.velocitypowered.api.permission.PermissionFunction;
|
||||||
@ -57,14 +60,17 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler;
|
||||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
|
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
|
||||||
|
import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
|
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
|
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
||||||
@ -105,6 +111,7 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||||||
import net.kyori.adventure.audience.MessageType;
|
import net.kyori.adventure.audience.MessageType;
|
||||||
import net.kyori.adventure.bossbar.BossBar;
|
import net.kyori.adventure.bossbar.BossBar;
|
||||||
import net.kyori.adventure.identity.Identity;
|
import net.kyori.adventure.identity.Identity;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.permission.PermissionChecker;
|
import net.kyori.adventure.permission.PermissionChecker;
|
||||||
import net.kyori.adventure.platform.facet.FacetPointers;
|
import net.kyori.adventure.platform.facet.FacetPointers;
|
||||||
import net.kyori.adventure.platform.facet.FacetPointers.Type;
|
import net.kyori.adventure.platform.facet.FacetPointers.Type;
|
||||||
@ -801,7 +808,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
}, connection.eventLoop());
|
}, connection.eventLoop());
|
||||||
} else if (event.getResult() instanceof final Notify res) {
|
} else if (event.getResult() instanceof final Notify res) {
|
||||||
if (event.kickedDuringServerConnect() && previousConnection != null) {
|
if (event.kickedDuringServerConnect() && previousConnection != null) {
|
||||||
sendMessage(Identity.nil(), res.getMessageComponent());
|
sendMessage(res.getMessageComponent());
|
||||||
} else {
|
} else {
|
||||||
disconnect(res.getMessageComponent());
|
disconnect(res.getMessageComponent());
|
||||||
}
|
}
|
||||||
@ -1008,6 +1015,50 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeCookie(final Key key, final byte[] data) {
|
||||||
|
Preconditions.checkNotNull(key);
|
||||||
|
Preconditions.checkNotNull(data);
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5),
|
||||||
|
"Player version must be at least 1.20.5 to be able to store cookies");
|
||||||
|
|
||||||
|
if (connection.getState() != StateRegistry.PLAY
|
||||||
|
&& connection.getState() != StateRegistry.CONFIG) {
|
||||||
|
throw new IllegalStateException("Can only store cookie in CONFIGURATION or PLAY protocol");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.getEventManager().fire(new CookieStoreEvent(this, key, data))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
final byte[] resultedData = event.getResult().getData() == null
|
||||||
|
? event.getOriginalData() : event.getResult().getData();
|
||||||
|
|
||||||
|
connection.write(new ClientboundStoreCookiePacket(resultedKey, resultedData));
|
||||||
|
}
|
||||||
|
}, connection.eventLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestCookie(final Key key) {
|
||||||
|
Preconditions.checkNotNull(key);
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5),
|
||||||
|
"Player version must be at least 1.20.5 to be able to retrieve cookies");
|
||||||
|
|
||||||
|
server.getEventManager().fire(new CookieRequestEvent(this, key))
|
||||||
|
.thenAcceptAsync(event -> {
|
||||||
|
if (event.getResult().isAllowed()) {
|
||||||
|
final Key resultedKey = event.getResult().getKey() == null
|
||||||
|
? event.getOriginalKey() : event.getResult().getKey();
|
||||||
|
|
||||||
|
connection.write(new ClientboundCookieRequestPacket(resultedKey));
|
||||||
|
}
|
||||||
|
}, connection.eventLoop());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCustomChatCompletions(@NotNull Collection<String> completions) {
|
public void addCustomChatCompletions(@NotNull Collection<String> completions) {
|
||||||
Preconditions.checkNotNull(completions, "completions");
|
Preconditions.checkNotNull(completions, "completions");
|
||||||
@ -1175,11 +1226,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
connection.write(StartUpdatePacket.INSTANCE);
|
connection.write(StartUpdatePacket.INSTANCE);
|
||||||
connection.getChannel().pipeline()
|
connection.getChannel().pipeline()
|
||||||
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
|
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
|
||||||
// Make sure we don't send any play packets to the player after update start
|
// Make sure we don't send any play packets to the player after update start
|
||||||
connection.addPlayPacketQueueHandler();
|
connection.addPlayPacketQueueHandler();
|
||||||
|
server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(this, connectionInFlight));
|
||||||
}, connection.eventLoop()).exceptionally((ex) -> {
|
}, connection.eventLoop()).exceptionally((ex) -> {
|
||||||
logger.error("Error switching player connection to config state:", ex);
|
logger.error("Error switching player connection to config state", ex);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1314,24 +1366,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (status.getStatus()) {
|
switch (status.getStatus()) {
|
||||||
case ALREADY_CONNECTED:
|
case ALREADY_CONNECTED -> sendMessage(ConnectionMessages.ALREADY_CONNECTED);
|
||||||
sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED);
|
case CONNECTION_IN_PROGRESS -> sendMessage(ConnectionMessages.IN_PROGRESS);
|
||||||
break;
|
case CONNECTION_CANCELLED -> {
|
||||||
case CONNECTION_IN_PROGRESS:
|
|
||||||
sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS);
|
|
||||||
break;
|
|
||||||
case CONNECTION_CANCELLED:
|
|
||||||
// Ignored; the plugin probably already handled this.
|
// Ignored; the plugin probably already handled this.
|
||||||
break;
|
}
|
||||||
case SERVER_DISCONNECTED:
|
case SERVER_DISCONNECTED -> {
|
||||||
Component reason = status.getReasonComponent()
|
final Component reason = status.getReasonComponent()
|
||||||
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
|
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
|
||||||
handleConnectionException(toConnect,
|
handleConnectionException(toConnect,
|
||||||
DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe());
|
DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe());
|
||||||
break;
|
}
|
||||||
default:
|
default -> {
|
||||||
// The only remaining value is successful (no need to do anything!)
|
// The only remaining value is successful (no need to do anything!)
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}, connection.eventLoop()).thenApply(Result::isSuccessful);
|
}, connection.eventLoop()).thenApply(Result::isSuccessful);
|
||||||
}
|
}
|
||||||
|
@ -68,9 +68,13 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
|||||||
this.pingReceived = true;
|
this.pingReceived = true;
|
||||||
server.getServerListPingHandler().getInitialPing(this.inbound)
|
server.getServerListPingHandler().getInitialPing(this.inbound)
|
||||||
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
||||||
.thenAcceptAsync(event -> connection.closeWith(
|
.thenAcceptAsync(event -> {
|
||||||
LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())),
|
if (event.getResult().isAllowed()) {
|
||||||
connection.eventLoop())
|
connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion()));
|
||||||
|
} else {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}, connection.eventLoop())
|
||||||
.exceptionally((ex) -> {
|
.exceptionally((ex) -> {
|
||||||
logger.error("Exception while handling legacy ping {}", packet, ex);
|
logger.error("Exception while handling legacy ping {}", packet, ex);
|
||||||
return null;
|
return null;
|
||||||
@ -95,10 +99,14 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
|||||||
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
||||||
.thenAcceptAsync(
|
.thenAcceptAsync(
|
||||||
(event) -> {
|
(event) -> {
|
||||||
final StringBuilder json = new StringBuilder();
|
if (event.getResult().isAllowed()) {
|
||||||
VelocityServer.getPingGsonInstance(connection.getProtocolVersion())
|
final StringBuilder json = new StringBuilder();
|
||||||
.toJson(event.getPing(), json);
|
VelocityServer.getPingGsonInstance(connection.getProtocolVersion())
|
||||||
connection.write(new StatusResponsePacket(json));
|
.toJson(event.getPing(), json);
|
||||||
|
connection.write(new StatusResponsePacket(json));
|
||||||
|
} else {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
connection.eventLoop())
|
connection.eventLoop())
|
||||||
.exceptionally((ex) -> {
|
.exceptionally((ex) -> {
|
||||||
|
@ -15,10 +15,11 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.player.bundle;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.connection.player;
|
package com.velocitypowered.proxy.connection.player.resourcepack;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
package com.velocitypowered.proxy.connection.player.resourcepack.handler;
|
||||||
|
|
||||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
@ -15,13 +15,14 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
package com.velocitypowered.proxy.connection.player.resourcepack.handler;
|
||||||
|
|
||||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
|
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -109,7 +110,7 @@ public sealed class LegacyResourcePackHandler extends ResourcePackHandler
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
onResourcePackResponse(new ResourcePackResponseBundle(queued.getId(),
|
onResourcePackResponse(new ResourcePackResponseBundle(queued.getId(),
|
||||||
new String(queued.getHash()),
|
queued.getHash() == null ? "" : new String(queued.getHash()),
|
||||||
PlayerResourcePackStatusEvent.Status.DECLINED));
|
PlayerResourcePackStatusEvent.Status.DECLINED));
|
||||||
queued = null;
|
queued = null;
|
||||||
}
|
}
|
||||||
@ -172,6 +173,10 @@ public sealed class LegacyResourcePackHandler extends ResourcePackHandler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPackAppliedByHash(final byte[] hash) {
|
public boolean hasPackAppliedByHash(final byte[] hash) {
|
||||||
|
if (hash == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return this.appliedResourcePack != null
|
return this.appliedResourcePack != null
|
||||||
&& Arrays.equals(this.appliedResourcePack.getHash(), hash);
|
&& Arrays.equals(this.appliedResourcePack.getHash(), hash);
|
||||||
}
|
}
|
@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
package com.velocitypowered.proxy.connection.player.resourcepack.handler;
|
||||||
|
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
import com.google.common.collect.Multimaps;
|
import com.google.common.collect.Multimaps;
|
||||||
@ -23,6 +23,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
|||||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
|
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
@ -15,14 +15,15 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.connection.player.resourcepack;
|
package com.velocitypowered.proxy.connection.player.resourcepack.handler;
|
||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
|
||||||
|
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
@ -35,7 +35,8 @@ public class Connections {
|
|||||||
public static final String MINECRAFT_DECODER = "minecraft-decoder";
|
public static final String MINECRAFT_DECODER = "minecraft-decoder";
|
||||||
public static final String MINECRAFT_ENCODER = "minecraft-encoder";
|
public static final String MINECRAFT_ENCODER = "minecraft-encoder";
|
||||||
public static final String READ_TIMEOUT = "read-timeout";
|
public static final String READ_TIMEOUT = "read-timeout";
|
||||||
public static final String PLAY_PACKET_QUEUE = "play-packet-queue";
|
public static final String PLAY_PACKET_QUEUE_OUTBOUND = "play-packet-queue-outbound";
|
||||||
|
public static final String PLAY_PACKET_QUEUE_INBOUND = "play-packet-queue-inbound";
|
||||||
|
|
||||||
private Connections() {
|
private Connections() {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
|
@ -36,6 +36,7 @@ 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_20_2;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
|
||||||
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_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_8;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
||||||
@ -52,6 +53,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
|
||||||
@ -73,6 +76,7 @@ import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
|
import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
|
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
|
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
|
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
|
||||||
@ -91,6 +95,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatP
|
|||||||
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket;
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerCommandPacket;
|
import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerCommandPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
|
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
|
||||||
@ -146,6 +152,9 @@ public enum StateRegistry {
|
|||||||
serverbound.register(
|
serverbound.register(
|
||||||
ClientSettingsPacket.class, ClientSettingsPacket::new,
|
ClientSettingsPacket.class, ClientSettingsPacket::new,
|
||||||
map(0x00, MINECRAFT_1_20_2, false));
|
map(0x00, MINECRAFT_1_20_2, false));
|
||||||
|
serverbound.register(
|
||||||
|
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
|
||||||
|
map(0x01, MINECRAFT_1_20_5, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
PluginMessagePacket.class, PluginMessagePacket::new,
|
PluginMessagePacket.class, PluginMessagePacket::new,
|
||||||
map(0x01, MINECRAFT_1_20_2, false),
|
map(0x01, MINECRAFT_1_20_2, false),
|
||||||
@ -171,6 +180,9 @@ public enum StateRegistry {
|
|||||||
KnownPacksPacket::new,
|
KnownPacksPacket::new,
|
||||||
map(0x07, MINECRAFT_1_20_5, false));
|
map(0x07, MINECRAFT_1_20_5, false));
|
||||||
|
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
|
||||||
|
map(0x00, MINECRAFT_1_20_5, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
PluginMessagePacket.class, PluginMessagePacket::new,
|
PluginMessagePacket.class, PluginMessagePacket::new,
|
||||||
map(0x00, MINECRAFT_1_20_2, false),
|
map(0x00, MINECRAFT_1_20_2, false),
|
||||||
@ -202,6 +214,9 @@ public enum StateRegistry {
|
|||||||
map(0x06, MINECRAFT_1_20_2, false),
|
map(0x06, MINECRAFT_1_20_2, false),
|
||||||
map(0x07, MINECRAFT_1_20_3, false),
|
map(0x07, MINECRAFT_1_20_3, false),
|
||||||
map(0x09, MINECRAFT_1_20_5, false));
|
map(0x09, MINECRAFT_1_20_5, false));
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
||||||
|
map(0x0A, MINECRAFT_1_20_5, false));
|
||||||
clientbound.register(TransferPacket.class, TransferPacket::new,
|
clientbound.register(TransferPacket.class, TransferPacket::new,
|
||||||
map(0x0B, MINECRAFT_1_20_5, false));
|
map(0x0B, MINECRAFT_1_20_5, false));
|
||||||
clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new,
|
clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new,
|
||||||
@ -214,6 +229,10 @@ public enum StateRegistry {
|
|||||||
map(0x0D, MINECRAFT_1_20_5, false));
|
map(0x0D, MINECRAFT_1_20_5, false));
|
||||||
clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new,
|
clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new,
|
||||||
map(0x0E, MINECRAFT_1_20_5, false));
|
map(0x0E, MINECRAFT_1_20_5, false));
|
||||||
|
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
|
||||||
|
map(0x0F, MINECRAFT_1_21, false));
|
||||||
|
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
|
||||||
|
map(0x10, MINECRAFT_1_21, false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PLAY {
|
PLAY {
|
||||||
@ -276,6 +295,9 @@ public enum StateRegistry {
|
|||||||
map(0x08, MINECRAFT_1_19_4, false),
|
map(0x08, MINECRAFT_1_19_4, false),
|
||||||
map(0x09, MINECRAFT_1_20_2, false),
|
map(0x09, MINECRAFT_1_20_2, false),
|
||||||
map(0x0A, MINECRAFT_1_20_5, false));
|
map(0x0A, MINECRAFT_1_20_5, false));
|
||||||
|
serverbound.register(
|
||||||
|
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
|
||||||
|
map(0x11, MINECRAFT_1_20_5, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
PluginMessagePacket.class,
|
PluginMessagePacket.class,
|
||||||
PluginMessagePacket::new,
|
PluginMessagePacket::new,
|
||||||
@ -374,6 +396,9 @@ public enum StateRegistry {
|
|||||||
map(0x0E, MINECRAFT_1_19_3, false),
|
map(0x0E, MINECRAFT_1_19_3, false),
|
||||||
map(0x10, MINECRAFT_1_19_4, false),
|
map(0x10, MINECRAFT_1_19_4, false),
|
||||||
map(0x11, MINECRAFT_1_20_2, false));
|
map(0x11, MINECRAFT_1_20_2, false));
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
|
||||||
|
map(0x16, MINECRAFT_1_20_5, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
PluginMessagePacket.class,
|
PluginMessagePacket.class,
|
||||||
PluginMessagePacket::new,
|
PluginMessagePacket::new,
|
||||||
@ -595,6 +620,9 @@ public enum StateRegistry {
|
|||||||
map(0x3A, MINECRAFT_1_19_4, false),
|
map(0x3A, MINECRAFT_1_19_4, false),
|
||||||
map(0x3C, MINECRAFT_1_20_2, false),
|
map(0x3C, MINECRAFT_1_20_2, false),
|
||||||
map(0x3E, MINECRAFT_1_20_5, false));
|
map(0x3E, MINECRAFT_1_20_5, false));
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
||||||
|
map(0x6B, MINECRAFT_1_20_5, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
SystemChatPacket.class,
|
SystemChatPacket.class,
|
||||||
SystemChatPacket::new,
|
SystemChatPacket::new,
|
||||||
@ -638,6 +666,10 @@ public enum StateRegistry {
|
|||||||
TransferPacket::new,
|
TransferPacket::new,
|
||||||
map(0x73, MINECRAFT_1_20_5, false)
|
map(0x73, MINECRAFT_1_20_5, false)
|
||||||
);
|
);
|
||||||
|
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
|
||||||
|
map(0x7A, MINECRAFT_1_21, false));
|
||||||
|
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
|
||||||
|
map(0x7B, MINECRAFT_1_21, false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LOGIN {
|
LOGIN {
|
||||||
@ -654,6 +686,9 @@ public enum StateRegistry {
|
|||||||
serverbound.register(
|
serverbound.register(
|
||||||
LoginAcknowledgedPacket.class, LoginAcknowledgedPacket::new,
|
LoginAcknowledgedPacket.class, LoginAcknowledgedPacket::new,
|
||||||
map(0x03, MINECRAFT_1_20_2, false));
|
map(0x03, MINECRAFT_1_20_2, false));
|
||||||
|
serverbound.register(
|
||||||
|
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
|
||||||
|
map(0x04, MINECRAFT_1_20_5, false));
|
||||||
|
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
DisconnectPacket.class, () -> new DisconnectPacket(this),
|
DisconnectPacket.class, () -> new DisconnectPacket(this),
|
||||||
@ -671,6 +706,9 @@ public enum StateRegistry {
|
|||||||
LoginPluginMessagePacket.class,
|
LoginPluginMessagePacket.class,
|
||||||
LoginPluginMessagePacket::new,
|
LoginPluginMessagePacket::new,
|
||||||
map(0x04, MINECRAFT_1_13, false));
|
map(0x04, MINECRAFT_1_13, false));
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
|
||||||
|
map(0x05, MINECRAFT_1_20_5, false));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,8 +56,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
if (msg instanceof ByteBuf) {
|
if (msg instanceof ByteBuf buf) {
|
||||||
ByteBuf buf = (ByteBuf) msg;
|
|
||||||
tryDecode(ctx, buf);
|
tryDecode(ctx, buf);
|
||||||
} else {
|
} else {
|
||||||
ctx.fireChannelRead(msg);
|
ctx.fireChannelRead(msg);
|
||||||
@ -147,4 +146,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
this.setProtocolVersion(registry.version);
|
this.setProtocolVersion(registry.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProtocolUtils.Direction getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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 PlayPacketQueueInboundHandler extends ChannelDuplexHandler {
|
||||||
|
|
||||||
|
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
|
||||||
|
private final Queue<Object> queue = PlatformDependent.newMpscQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides registries for client & server bound packets.
|
||||||
|
*
|
||||||
|
* @param version the protocol version
|
||||||
|
*/
|
||||||
|
public PlayPacketQueueInboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
|
||||||
|
this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
if (msg instanceof final MinecraftPacket packet) {
|
||||||
|
// If the packet exists in the CONFIG state, we want to always
|
||||||
|
// ensure that it gets handled by the current handler
|
||||||
|
if (this.registry.containsPacket(packet)) {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, queue the packet
|
||||||
|
this.queue.offer(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) {
|
||||||
|
// Handle all the queued packets
|
||||||
|
Object msg;
|
||||||
|
while ((msg = this.queue.poll()) != null) {
|
||||||
|
if (active) {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
} else {
|
||||||
|
ReferenceCountUtil.release(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
* <p>This handler will queue up any packets that are sent to the client during this time, and send
|
* <p>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.
|
* them once the client has (re)entered the PLAY state.
|
||||||
*/
|
*/
|
||||||
public class PlayPacketQueueHandler extends ChannelDuplexHandler {
|
public class PlayPacketQueueOutboundHandler extends ChannelDuplexHandler {
|
||||||
|
|
||||||
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
|
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
|
||||||
private final Queue<MinecraftPacket> queue = PlatformDependent.newMpscQueue();
|
private final Queue<MinecraftPacket> queue = PlatformDependent.newMpscQueue();
|
||||||
@ -50,28 +50,26 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler {
|
|||||||
*
|
*
|
||||||
* @param version the protocol version
|
* @param version the protocol version
|
||||||
*/
|
*/
|
||||||
public PlayPacketQueueHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
|
public PlayPacketQueueOutboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
|
||||||
this.registry =
|
this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version);
|
||||||
StateRegistry.CONFIG.getProtocolRegistry(direction, version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
throws Exception {
|
if (!(msg instanceof final MinecraftPacket packet)) {
|
||||||
if (!(msg instanceof MinecraftPacket)) {
|
|
||||||
ctx.write(msg, promise);
|
ctx.write(msg, promise);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the packet exists in the CONFIG state, we want to always
|
// If the packet exists in the CONFIG state, we want to always
|
||||||
// ensure that it gets sent out to the client
|
// ensure that it gets sent out to the client
|
||||||
if (this.registry.containsPacket(((MinecraftPacket) msg))) {
|
if (this.registry.containsPacket(packet)) {
|
||||||
ctx.write(msg, promise);
|
ctx.write(msg, promise);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, queue the packet
|
// Otherwise, queue the packet
|
||||||
this.queue.offer((MinecraftPacket) msg);
|
this.queue.offer(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -87,10 +85,6 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void releaseQueue(ChannelHandlerContext ctx, boolean active) {
|
private void releaseQueue(ChannelHandlerContext ctx, boolean active) {
|
||||||
if (this.queue.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send out all the queued packets
|
// Send out all the queued packets
|
||||||
MinecraftPacket packet;
|
MinecraftPacket packet;
|
||||||
while ((packet = this.queue.poll()) != null) {
|
while ((packet = this.queue.poll()) != null) {
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
|
||||||
|
public class ClientboundCookieRequestPacket implements MinecraftPacket {
|
||||||
|
|
||||||
|
private Key key;
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientboundCookieRequestPacket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientboundCookieRequestPacket(final Key key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
this.key = ProtocolUtils.readKey(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
ProtocolUtils.writeKey(buf, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
|
return handler.handle(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
|
||||||
|
public class ClientboundStoreCookiePacket implements MinecraftPacket {
|
||||||
|
|
||||||
|
private Key key;
|
||||||
|
private byte[] payload;
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientboundStoreCookiePacket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientboundStoreCookiePacket(final Key key, final byte[] payload) {
|
||||||
|
this.key = key;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
this.key = ProtocolUtils.readKey(buf);
|
||||||
|
this.payload = ProtocolUtils.readByteArray(buf, 5120);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
ProtocolUtils.writeKey(buf, key);
|
||||||
|
ProtocolUtils.writeByteArray(buf, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
|
return handler.handle(this);
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ import com.google.common.base.Preconditions;
|
|||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||||
|
@ -92,7 +92,7 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
|
|||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
|
||||||
properties = ProtocolUtils.readProperties(buf);
|
properties = ProtocolUtils.readProperties(buf);
|
||||||
}
|
}
|
||||||
if (version == ProtocolVersion.MINECRAFT_1_20_5) {
|
if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
|
||||||
buf.readBoolean();
|
buf.readBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
|
|||||||
ProtocolUtils.writeProperties(buf, properties);
|
ProtocolUtils.writeProperties(buf, properties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (version == ProtocolVersion.MINECRAFT_1_20_5) {
|
if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
|
||||||
buf.writeBoolean(strictErrorHandling);
|
buf.writeBoolean(strictErrorHandling);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
public class ServerboundCookieResponsePacket implements MinecraftPacket {
|
||||||
|
|
||||||
|
private Key key;
|
||||||
|
private byte @Nullable [] payload;
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte @Nullable [] getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerboundCookieResponsePacket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerboundCookieResponsePacket(final Key key, final byte @Nullable [] payload) {
|
||||||
|
this.key = key;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
this.key = ProtocolUtils.readKey(buf);
|
||||||
|
if (buf.readBoolean()) {
|
||||||
|
this.payload = ProtocolUtils.readByteArray(buf, 5120);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
ProtocolUtils.writeKey(buf, key);
|
||||||
|
final boolean hasPayload = payload != null && payload.length > 0;
|
||||||
|
buf.writeBoolean(hasPayload);
|
||||||
|
if (hasPayload) {
|
||||||
|
ProtocolUtils.writeByteArray(buf, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
|
return handler.handle(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ClientboundCustomReportDetailsPacket implements MinecraftPacket {
|
||||||
|
|
||||||
|
private Map<String, String> details;
|
||||||
|
|
||||||
|
public ClientboundCustomReportDetailsPacket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientboundCustomReportDetailsPacket(Map<String, String> details) {
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
int detailsCount = ProtocolUtils.readVarInt(buf);
|
||||||
|
|
||||||
|
this.details = new HashMap<>(detailsCount);
|
||||||
|
for (int i = 0; i < detailsCount; i++) {
|
||||||
|
details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, details.size());
|
||||||
|
|
||||||
|
details.forEach((key, detail) -> {
|
||||||
|
ProtocolUtils.writeString(buf, key);
|
||||||
|
ProtocolUtils.writeString(buf, detail);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
|
return handler.handle(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getDetails() {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.packet.chat.ComponentHolder;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ClientboundServerLinksPacket implements MinecraftPacket {
|
||||||
|
|
||||||
|
private List<ServerLink> serverLinks;
|
||||||
|
|
||||||
|
public ClientboundServerLinksPacket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientboundServerLinksPacket(List<ServerLink> serverLinks) {
|
||||||
|
this.serverLinks = serverLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||||
|
int linksCount = ProtocolUtils.readVarInt(buf);
|
||||||
|
|
||||||
|
this.serverLinks = new ArrayList<>(linksCount);
|
||||||
|
for (int i = 0; i < linksCount; i++) {
|
||||||
|
serverLinks.add(ServerLink.read(buf, version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, serverLinks.size());
|
||||||
|
|
||||||
|
for (ServerLink serverLink : serverLinks) {
|
||||||
|
serverLink.write(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
|
return handler.handle(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ServerLink> getServerLinks() {
|
||||||
|
return serverLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ServerLink(int id, ComponentHolder displayName, String url) {
|
||||||
|
private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
|
||||||
|
if (buf.readBoolean()) {
|
||||||
|
return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));
|
||||||
|
} else {
|
||||||
|
return new ServerLink(-1, ComponentHolder.read(buf, version), ProtocolUtils.readString(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(ByteBuf buf) {
|
||||||
|
if (id >= 0) {
|
||||||
|
buf.writeBoolean(true);
|
||||||
|
ProtocolUtils.writeVarInt(buf, id);
|
||||||
|
} else {
|
||||||
|
buf.writeBoolean(false);
|
||||||
|
displayName.write(buf);
|
||||||
|
}
|
||||||
|
ProtocolUtils.writeString(buf, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren