Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-17 05:20:14 +01:00
Merge branch 'dev/1.1.0' into bungeequack-integrate
Dieser Commit ist enthalten in:
Commit
b08f27b5c4
4
.gitignore
vendored
4
.gitignore
vendored
@ -87,5 +87,5 @@ run/
|
|||||||
plugins/
|
plugins/
|
||||||
|
|
||||||
### Natives stuff ###
|
### Natives stuff ###
|
||||||
natives/mbedtls
|
native/mbedtls
|
||||||
natives/zlib-ng
|
native/zlib-ng
|
||||||
|
@ -8,3 +8,4 @@ cache:
|
|||||||
- $HOME/.gradle/wrapper/
|
- $HOME/.gradle/wrapper/
|
||||||
jdk:
|
jdk:
|
||||||
- openjdk8
|
- openjdk8
|
||||||
|
- openjdk11
|
@ -73,6 +73,9 @@ javadoc {
|
|||||||
|
|
||||||
// Disable the crazy super-strict doclint tool in Java 8
|
// Disable the crazy super-strict doclint tool in Java 8
|
||||||
options.addStringOption('Xdoclint:none', '-quiet')
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
|
||||||
|
// Mark sources as Java 8 source compatible
|
||||||
|
options.source = '8'
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@ -147,10 +147,12 @@ public final class KickedFromServerEvent implements
|
|||||||
*/
|
*/
|
||||||
public static final class RedirectPlayer implements ServerKickResult {
|
public static final class RedirectPlayer implements ServerKickResult {
|
||||||
|
|
||||||
|
private final Component message;
|
||||||
private final RegisteredServer server;
|
private final RegisteredServer server;
|
||||||
|
|
||||||
private RedirectPlayer(RegisteredServer server) {
|
private RedirectPlayer(RegisteredServer server, @Nullable Component message) {
|
||||||
this.server = Preconditions.checkNotNull(server, "server");
|
this.server = Preconditions.checkNotNull(server, "server");
|
||||||
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -162,14 +164,23 @@ public final class KickedFromServerEvent implements
|
|||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Component getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new redirect result to forward the player to the specified {@code server}.
|
* Creates a new redirect result to forward the player to the specified {@code server}.
|
||||||
*
|
*
|
||||||
* @param server the server to send the player to
|
* @param server the server to send the player to
|
||||||
* @return the redirect result
|
* @return the redirect result
|
||||||
*/
|
*/
|
||||||
public static RedirectPlayer create(RegisteredServer server) {
|
public static RedirectPlayer create(RegisteredServer server, @Nullable Component message) {
|
||||||
return new RedirectPlayer(server);
|
return new RedirectPlayer(server, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerKickResult create(RegisteredServer server) {
|
||||||
|
return create(server, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.velocitypowered.api.event.player;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when a player has finished connecting to the proxy and we need to choose the first server
|
||||||
|
* to connect to.
|
||||||
|
*/
|
||||||
|
public class PlayerChooseInitialServerEvent {
|
||||||
|
|
||||||
|
private final Player player;
|
||||||
|
private @Nullable RegisteredServer initialServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a PlayerChooseInitialServerEvent.
|
||||||
|
* @param player the player that was connected
|
||||||
|
* @param initialServer the initial server selected, may be {@code null}
|
||||||
|
*/
|
||||||
|
public PlayerChooseInitialServerEvent(Player player, @Nullable RegisteredServer initialServer) {
|
||||||
|
this.player = Preconditions.checkNotNull(player, "player");
|
||||||
|
this.initialServer = initialServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<RegisteredServer> getInitialServer() {
|
||||||
|
return Optional.ofNullable(initialServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new initial server.
|
||||||
|
* @param server the initial server the player should connect to
|
||||||
|
*/
|
||||||
|
public void setInitialServer(RegisteredServer server) {
|
||||||
|
this.initialServer = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PlayerChooseInitialServerEvent{"
|
||||||
|
+ "player=" + player
|
||||||
|
+ ", initialServer=" + initialServer
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ package com.velocitypowered.api.event.player;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is fired once the player has successfully connected to the target server and the
|
* This event is fired once the player has successfully connected to the target server and the
|
||||||
@ -12,10 +14,24 @@ public final class ServerConnectedEvent {
|
|||||||
|
|
||||||
private final Player player;
|
private final Player player;
|
||||||
private final RegisteredServer server;
|
private final RegisteredServer server;
|
||||||
|
private final @Nullable RegisteredServer previousServer;
|
||||||
|
|
||||||
public ServerConnectedEvent(Player player, RegisteredServer server) {
|
/**
|
||||||
|
* Constructs a ServerConnectedEvent.
|
||||||
|
* @param player the player that was connected
|
||||||
|
* @param server the server the player was connected to
|
||||||
|
* @param previousServer the server the player was previously connected to, null if none
|
||||||
|
*/
|
||||||
|
public ServerConnectedEvent(Player player, RegisteredServer server,
|
||||||
|
@Nullable RegisteredServer previousServer) {
|
||||||
this.player = Preconditions.checkNotNull(player, "player");
|
this.player = Preconditions.checkNotNull(player, "player");
|
||||||
this.server = Preconditions.checkNotNull(server, "server");
|
this.server = Preconditions.checkNotNull(server, "server");
|
||||||
|
this.previousServer = previousServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public ServerConnectedEvent(Player player, RegisteredServer server) {
|
||||||
|
this(player, server, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
@ -26,11 +42,16 @@ public final class ServerConnectedEvent {
|
|||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<RegisteredServer> getPreviousServer() {
|
||||||
|
return Optional.ofNullable(previousServer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ServerConnectedEvent{"
|
return "ServerConnectedEvent{"
|
||||||
+ "player=" + player
|
+ "player=" + player
|
||||||
+ ", server=" + server
|
+ ", server=" + server
|
||||||
|
+ ", previousServer=" + previousServer
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,8 @@ public enum ProtocolVersion {
|
|||||||
MINECRAFT_1_14_1(480, "1.14.1"),
|
MINECRAFT_1_14_1(480, "1.14.1"),
|
||||||
MINECRAFT_1_14_2(485, "1.14.2"),
|
MINECRAFT_1_14_2(485, "1.14.2"),
|
||||||
MINECRAFT_1_14_3(490, "1.14.3"),
|
MINECRAFT_1_14_3(490, "1.14.3"),
|
||||||
MINECRAFT_1_14_4(498, "1.14.4");
|
MINECRAFT_1_14_4(498, "1.14.4"),
|
||||||
|
MINECRAFT_1_15(566, "1.15-pre2");
|
||||||
|
|
||||||
private final int protocol;
|
private final int protocol;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
195
api/src/main/java/com/velocitypowered/api/util/FastUuidSansHyphens.java
Normale Datei
195
api/src/main/java/com/velocitypowered/api/util/FastUuidSansHyphens.java
Normale Datei
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Jon Chambers
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.api.util;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a modified FastUUID implementation. The primary difference is that it does not dash its
|
||||||
|
* UUIDs. As the native Java 9+ UUID.toString() implementation dashes its UUIDs, we use the FastUUID
|
||||||
|
* internal method, which ought to be faster than a String.replace().
|
||||||
|
*/
|
||||||
|
class FastUuidSansHyphens {
|
||||||
|
|
||||||
|
private static final int MOJANG_BROKEN_UUID_LENGTH = 32;
|
||||||
|
|
||||||
|
private static final char[] HEX_DIGITS =
|
||||||
|
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||||
|
|
||||||
|
private static final long[] HEX_VALUES = new long[128];
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (int i = 0; i < HEX_VALUES.length; i++) {
|
||||||
|
HEX_VALUES[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
HEX_VALUES['0'] = 0x0;
|
||||||
|
HEX_VALUES['1'] = 0x1;
|
||||||
|
HEX_VALUES['2'] = 0x2;
|
||||||
|
HEX_VALUES['3'] = 0x3;
|
||||||
|
HEX_VALUES['4'] = 0x4;
|
||||||
|
HEX_VALUES['5'] = 0x5;
|
||||||
|
HEX_VALUES['6'] = 0x6;
|
||||||
|
HEX_VALUES['7'] = 0x7;
|
||||||
|
HEX_VALUES['8'] = 0x8;
|
||||||
|
HEX_VALUES['9'] = 0x9;
|
||||||
|
|
||||||
|
HEX_VALUES['a'] = 0xa;
|
||||||
|
HEX_VALUES['b'] = 0xb;
|
||||||
|
HEX_VALUES['c'] = 0xc;
|
||||||
|
HEX_VALUES['d'] = 0xd;
|
||||||
|
HEX_VALUES['e'] = 0xe;
|
||||||
|
HEX_VALUES['f'] = 0xf;
|
||||||
|
|
||||||
|
HEX_VALUES['A'] = 0xa;
|
||||||
|
HEX_VALUES['B'] = 0xb;
|
||||||
|
HEX_VALUES['C'] = 0xc;
|
||||||
|
HEX_VALUES['D'] = 0xd;
|
||||||
|
HEX_VALUES['E'] = 0xe;
|
||||||
|
HEX_VALUES['F'] = 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FastUuidSansHyphens() {
|
||||||
|
// A private constructor prevents callers from accidentally instantiating FastUUID instances
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a UUID from the given character sequence. The character sequence must represent a
|
||||||
|
* Mojang UUID.
|
||||||
|
*
|
||||||
|
* @param uuidSequence the character sequence from which to parse a UUID
|
||||||
|
*
|
||||||
|
* @return the UUID represented by the given character sequence
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the given character sequence does not conform to the string
|
||||||
|
* representation of a Mojang UUID.
|
||||||
|
*/
|
||||||
|
static UUID parseUuid(final CharSequence uuidSequence) {
|
||||||
|
if (uuidSequence.length() != MOJANG_BROKEN_UUID_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Illegal UUID string: " + uuidSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
long mostSignificantBits = getHexValueForChar(uuidSequence.charAt(0)) << 60;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(1)) << 56;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(2)) << 52;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(3)) << 48;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(4)) << 44;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(5)) << 40;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(6)) << 36;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(7)) << 32;
|
||||||
|
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(8)) << 28;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(9)) << 24;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(10)) << 20;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(11)) << 16;
|
||||||
|
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(12)) << 12;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(13)) << 8;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(14)) << 4;
|
||||||
|
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(15));
|
||||||
|
|
||||||
|
long leastSignificantBits = getHexValueForChar(uuidSequence.charAt(16)) << 60;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(17)) << 56;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(18)) << 52;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(19)) << 48;
|
||||||
|
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(20)) << 44;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(21)) << 40;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(22)) << 36;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(23)) << 32;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(24)) << 28;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(25)) << 24;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(26)) << 20;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(27)) << 16;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(28)) << 12;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(29)) << 8;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(30)) << 4;
|
||||||
|
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(31));
|
||||||
|
|
||||||
|
return new UUID(mostSignificantBits, leastSignificantBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the given UUID. The returned string is formatted as a
|
||||||
|
* Mojang-style UUID.
|
||||||
|
*
|
||||||
|
* @param uuid the UUID to represent as a string
|
||||||
|
*
|
||||||
|
* @return a string representation of the given UUID
|
||||||
|
*/
|
||||||
|
public static String toString(final UUID uuid) {
|
||||||
|
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||||
|
final long leastSignificantBits = uuid.getLeastSignificantBits();
|
||||||
|
|
||||||
|
final char[] uuidChars = new char[MOJANG_BROKEN_UUID_LENGTH];
|
||||||
|
|
||||||
|
uuidChars[0] = HEX_DIGITS[(int) ((mostSignificantBits & 0xf000000000000000L) >>> 60)];
|
||||||
|
uuidChars[1] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0f00000000000000L) >>> 56)];
|
||||||
|
uuidChars[2] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00f0000000000000L) >>> 52)];
|
||||||
|
uuidChars[3] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000f000000000000L) >>> 48)];
|
||||||
|
uuidChars[4] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000f00000000000L) >>> 44)];
|
||||||
|
uuidChars[5] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000f0000000000L) >>> 40)];
|
||||||
|
uuidChars[6] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000f000000000L) >>> 36)];
|
||||||
|
uuidChars[7] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000f00000000L) >>> 32)];
|
||||||
|
uuidChars[8] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000f0000000L) >>> 28)];
|
||||||
|
uuidChars[9] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000f000000L) >>> 24)];
|
||||||
|
uuidChars[10] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000f00000L) >>> 20)];
|
||||||
|
uuidChars[11] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000f0000L) >>> 16)];
|
||||||
|
uuidChars[12] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000000f000L) >>> 12)];
|
||||||
|
uuidChars[13] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000000f00L) >>> 8)];
|
||||||
|
uuidChars[14] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000000f0L) >>> 4)];
|
||||||
|
uuidChars[15] = HEX_DIGITS[(int) (mostSignificantBits & 0x000000000000000fL)];
|
||||||
|
uuidChars[16] = HEX_DIGITS[(int) ((leastSignificantBits & 0xf000000000000000L) >>> 60)];
|
||||||
|
uuidChars[17] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0f00000000000000L) >>> 56)];
|
||||||
|
uuidChars[18] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00f0000000000000L) >>> 52)];
|
||||||
|
uuidChars[19] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000f000000000000L) >>> 48)];
|
||||||
|
uuidChars[20] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000f00000000000L) >>> 44)];
|
||||||
|
uuidChars[21] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000f0000000000L) >>> 40)];
|
||||||
|
uuidChars[22] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000f000000000L) >>> 36)];
|
||||||
|
uuidChars[23] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000f00000000L) >>> 32)];
|
||||||
|
uuidChars[24] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000f0000000L) >>> 28)];
|
||||||
|
uuidChars[25] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000000f000000L) >>> 24)];
|
||||||
|
uuidChars[26] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000000f00000L) >>> 20)];
|
||||||
|
uuidChars[27] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000000f0000L) >>> 16)];
|
||||||
|
uuidChars[28] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000000000f000L) >>> 12)];
|
||||||
|
uuidChars[29] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000000000f00L) >>> 8)];
|
||||||
|
uuidChars[30] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000000000f0L) >>> 4)];
|
||||||
|
uuidChars[31] = HEX_DIGITS[(int) (leastSignificantBits & 0x000000000000000fL)];
|
||||||
|
|
||||||
|
return new String(uuidChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getHexValueForChar(final char c) {
|
||||||
|
try {
|
||||||
|
if (HEX_VALUES[c] < 0) {
|
||||||
|
throw new IllegalArgumentException("Illegal hexadecimal digit: " + c);
|
||||||
|
}
|
||||||
|
} catch (final ArrayIndexOutOfBoundsException e) {
|
||||||
|
throw new IllegalArgumentException("Illegal hexadecimal digit: " + c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HEX_VALUES[c];
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package com.velocitypowered.api.util;
|
package com.velocitypowered.api.util;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -23,11 +22,7 @@ public final class UuidUtils {
|
|||||||
*/
|
*/
|
||||||
public static UUID fromUndashed(final String string) {
|
public static UUID fromUndashed(final String string) {
|
||||||
Objects.requireNonNull(string, "string");
|
Objects.requireNonNull(string, "string");
|
||||||
Preconditions.checkArgument(string.length() == 32, "Length is incorrect");
|
return FastUuidSansHyphens.parseUuid(string);
|
||||||
return new UUID(
|
|
||||||
Long.parseUnsignedLong(string.substring(0, 16), 16),
|
|
||||||
Long.parseUnsignedLong(string.substring(16), 16)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,10 +33,7 @@ public final class UuidUtils {
|
|||||||
*/
|
*/
|
||||||
public static String toUndashed(final UUID uuid) {
|
public static String toUndashed(final UUID uuid) {
|
||||||
Preconditions.checkNotNull(uuid, "uuid");
|
Preconditions.checkNotNull(uuid, "uuid");
|
||||||
String msbStr = Long.toHexString(uuid.getMostSignificantBits());
|
return FastUuidSansHyphens.toString(uuid);
|
||||||
String lsbStr = Long.toHexString(uuid.getLeastSignificantBits());
|
|
||||||
return Strings.padStart(msbStr, 16, '0') + Strings.padStart(lsbStr,
|
|
||||||
16, '0');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +24,7 @@ allprojects {
|
|||||||
junitVersion = '5.3.0-M1'
|
junitVersion = '5.3.0-M1'
|
||||||
slf4jVersion = '1.7.25'
|
slf4jVersion = '1.7.25'
|
||||||
log4jVersion = '2.11.2'
|
log4jVersion = '2.11.2'
|
||||||
nettyVersion = '4.1.38.Final'
|
nettyVersion = '4.1.43.Final'
|
||||||
guavaVersion = '25.1-jre'
|
guavaVersion = '25.1-jre'
|
||||||
checkerFrameworkVersion = '2.7.0'
|
checkerFrameworkVersion = '2.7.0'
|
||||||
configurateVersion = '3.6'
|
configurateVersion = '3.6'
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.velocitypowered.natives;
|
package com.velocitypowered.natives;
|
||||||
|
|
||||||
|
import com.velocitypowered.natives.util.BufferPreference;
|
||||||
|
|
||||||
public interface Native {
|
public interface Native {
|
||||||
boolean isNative();
|
BufferPreference preferredBufferType();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,139 @@
|
|||||||
|
package com.velocitypowered.natives.compression;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFER_SIZE;
|
||||||
|
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
||||||
|
|
||||||
|
import com.velocitypowered.natives.util.BufferPreference;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.zip.DataFormatException;
|
||||||
|
import java.util.zip.Deflater;
|
||||||
|
import java.util.zip.Inflater;
|
||||||
|
|
||||||
|
public class Java11VelocityCompressor implements VelocityCompressor {
|
||||||
|
|
||||||
|
public static final VelocityCompressorFactory FACTORY = Java11VelocityCompressor::new;
|
||||||
|
|
||||||
|
// The use of MethodHandle is intentional. Velocity targets Java 8, and these methods don't exist
|
||||||
|
// in Java 8. This was also the most performant solution I could find, only slightly slower than a
|
||||||
|
// direct method call without long warmup times, requiring bytecode generation through ASM, or
|
||||||
|
// other stuff.
|
||||||
|
private static final MethodHandle DEFLATE_SET_INPUT;
|
||||||
|
private static final MethodHandle INFLATE_SET_INPUT;
|
||||||
|
private static final MethodHandle DEFLATE_CALL;
|
||||||
|
private static final MethodHandle INFLATE_CALL;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
DEFLATE_SET_INPUT = MethodHandles.lookup().findVirtual(Deflater.class, "setInput",
|
||||||
|
MethodType.methodType(void.class, ByteBuffer.class));
|
||||||
|
INFLATE_SET_INPUT = MethodHandles.lookup().findVirtual(Inflater.class, "setInput",
|
||||||
|
MethodType.methodType(void.class, ByteBuffer.class));
|
||||||
|
|
||||||
|
DEFLATE_CALL = MethodHandles.lookup().findVirtual(Deflater.class, "deflate",
|
||||||
|
MethodType.methodType(int.class, ByteBuffer.class));
|
||||||
|
INFLATE_CALL = MethodHandles.lookup().findVirtual(Inflater.class, "inflate",
|
||||||
|
MethodType.methodType(int.class, ByteBuffer.class));
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError("Can't use Java 11 compressor on your version of Java");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Deflater deflater;
|
||||||
|
private final Inflater inflater;
|
||||||
|
private boolean disposed = false;
|
||||||
|
|
||||||
|
private Java11VelocityCompressor(int level) {
|
||||||
|
this.deflater = new Deflater(level);
|
||||||
|
this.inflater = new Inflater();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
||||||
|
ensureNotDisposed();
|
||||||
|
|
||||||
|
// We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly.
|
||||||
|
checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers");
|
||||||
|
checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers");
|
||||||
|
|
||||||
|
try {
|
||||||
|
int origIdx = source.readerIndex();
|
||||||
|
INFLATE_SET_INPUT.invokeExact(inflater, source.nioBuffer());
|
||||||
|
|
||||||
|
while (!inflater.finished() && inflater.getBytesRead() < source.readableBytes()) {
|
||||||
|
if (!destination.isWritable()) {
|
||||||
|
ensureMaxSize(destination, max);
|
||||||
|
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer destNioBuf = destination.nioBuffer(destination.writerIndex(),
|
||||||
|
destination.writableBytes());
|
||||||
|
int produced = (int) INFLATE_CALL.invokeExact(inflater, destNioBuf);
|
||||||
|
source.readerIndex(origIdx + inflater.getTotalIn());
|
||||||
|
destination.writerIndex(destination.writerIndex() + produced);
|
||||||
|
}
|
||||||
|
|
||||||
|
inflater.reset();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (e instanceof DataFormatException) {
|
||||||
|
throw (DataFormatException) e;
|
||||||
|
}
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||||
|
ensureNotDisposed();
|
||||||
|
|
||||||
|
// We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly.
|
||||||
|
checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers");
|
||||||
|
checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers");
|
||||||
|
|
||||||
|
try {
|
||||||
|
int origIdx = source.readerIndex();
|
||||||
|
DEFLATE_SET_INPUT.invokeExact(deflater, source.nioBuffer());
|
||||||
|
deflater.finish();
|
||||||
|
|
||||||
|
while (!deflater.finished()) {
|
||||||
|
if (!destination.isWritable()) {
|
||||||
|
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer destNioBuf = destination.nioBuffer(destination.writerIndex(),
|
||||||
|
destination.writableBytes());
|
||||||
|
int produced = (int) DEFLATE_CALL.invokeExact(deflater, destNioBuf);
|
||||||
|
source.readerIndex(origIdx + deflater.getTotalIn());
|
||||||
|
destination.writerIndex(destination.writerIndex() + produced);
|
||||||
|
}
|
||||||
|
|
||||||
|
deflater.reset();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (e instanceof DataFormatException) {
|
||||||
|
throw (DataFormatException) e;
|
||||||
|
}
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
disposed = true;
|
||||||
|
deflater.end();
|
||||||
|
inflater.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureNotDisposed() {
|
||||||
|
checkState(!disposed, "Object already disposed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferPreference preferredBufferType() {
|
||||||
|
return BufferPreference.DIRECT_PREFERRED;
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFE
|
|||||||
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.natives.util.BufferPreference;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.util.zip.DataFormatException;
|
import java.util.zip.DataFormatException;
|
||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
@ -15,19 +16,37 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
|||||||
|
|
||||||
private final Deflater deflater;
|
private final Deflater deflater;
|
||||||
private final Inflater inflater;
|
private final Inflater inflater;
|
||||||
private final byte[] buf;
|
private byte[] buf = new byte[0];
|
||||||
private boolean disposed = false;
|
private boolean disposed = false;
|
||||||
|
|
||||||
private JavaVelocityCompressor(int level) {
|
private JavaVelocityCompressor(int level) {
|
||||||
this.deflater = new Deflater(level);
|
this.deflater = new Deflater(level);
|
||||||
this.inflater = new Inflater();
|
this.inflater = new Inflater();
|
||||||
this.buf = new byte[ZLIB_BUFFER_SIZE];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
||||||
ensureNotDisposed();
|
ensureNotDisposed();
|
||||||
|
|
||||||
|
final int available = source.readableBytes();
|
||||||
|
this.setInflaterInput(source);
|
||||||
|
|
||||||
|
if (destination.hasArray()) {
|
||||||
|
this.inflateDestinationIsHeap(destination, available, max);
|
||||||
|
} else {
|
||||||
|
if (buf.length == 0) {
|
||||||
|
buf = new byte[ZLIB_BUFFER_SIZE];
|
||||||
|
}
|
||||||
|
while (!inflater.finished() && inflater.getBytesRead() < available) {
|
||||||
|
ensureMaxSize(destination, max);
|
||||||
|
int read = inflater.inflate(buf);
|
||||||
|
destination.writeBytes(buf, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inflater.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setInflaterInput(ByteBuf source) {
|
||||||
final int available = source.readableBytes();
|
final int available = source.readableBytes();
|
||||||
if (source.hasArray()) {
|
if (source.hasArray()) {
|
||||||
inflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(), available);
|
inflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(), available);
|
||||||
@ -36,19 +55,45 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
|||||||
source.readBytes(inData);
|
source.readBytes(inData);
|
||||||
inflater.setInput(inData);
|
inflater.setInput(inData);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!inflater.finished() && inflater.getBytesRead() < available) {
|
|
||||||
ensureMaxSize(destination, max);
|
|
||||||
int read = inflater.inflate(buf);
|
|
||||||
destination.writeBytes(buf, 0, read);
|
|
||||||
}
|
}
|
||||||
inflater.reset();
|
|
||||||
|
private void inflateDestinationIsHeap(ByteBuf destination, int available, int max)
|
||||||
|
throws DataFormatException {
|
||||||
|
while (!inflater.finished() && inflater.getBytesRead() < available) {
|
||||||
|
if (!destination.isWritable()) {
|
||||||
|
ensureMaxSize(destination, max);
|
||||||
|
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureMaxSize(destination, max);
|
||||||
|
int produced = inflater.inflate(destination.array(), destination.arrayOffset()
|
||||||
|
+ destination.writerIndex(), destination.writableBytes());
|
||||||
|
destination.writerIndex(destination.writerIndex() + produced);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||||
ensureNotDisposed();
|
ensureNotDisposed();
|
||||||
|
|
||||||
|
this.setDeflaterInput(source);
|
||||||
|
deflater.finish();
|
||||||
|
|
||||||
|
if (destination.hasArray()) {
|
||||||
|
this.deflateDestinationIsHeap(destination);
|
||||||
|
} else {
|
||||||
|
if (buf.length == 0) {
|
||||||
|
buf = new byte[ZLIB_BUFFER_SIZE];
|
||||||
|
}
|
||||||
|
while (!deflater.finished()) {
|
||||||
|
int bytes = deflater.deflate(buf);
|
||||||
|
destination.writeBytes(buf, 0, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deflater.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDeflaterInput(ByteBuf source) {
|
||||||
if (source.hasArray()) {
|
if (source.hasArray()) {
|
||||||
deflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(),
|
deflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(),
|
||||||
source.readableBytes());
|
source.readableBytes());
|
||||||
@ -57,12 +102,18 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
|||||||
source.readBytes(inData);
|
source.readBytes(inData);
|
||||||
deflater.setInput(inData);
|
deflater.setInput(inData);
|
||||||
}
|
}
|
||||||
deflater.finish();
|
|
||||||
while (!deflater.finished()) {
|
|
||||||
int bytes = deflater.deflate(buf);
|
|
||||||
destination.writeBytes(buf, 0, bytes);
|
|
||||||
}
|
}
|
||||||
deflater.reset();
|
|
||||||
|
private void deflateDestinationIsHeap(ByteBuf destination) {
|
||||||
|
while (!deflater.finished()) {
|
||||||
|
if (!destination.isWritable()) {
|
||||||
|
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int produced = deflater.deflate(destination.array(), destination.arrayOffset()
|
||||||
|
+ destination.writerIndex(), destination.writableBytes());
|
||||||
|
destination.writerIndex(destination.writerIndex() + produced);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,7 +128,7 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNative() {
|
public BufferPreference preferredBufferType() {
|
||||||
return false;
|
return BufferPreference.HEAP_PREFERRED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFE
|
|||||||
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.natives.util.BufferPreference;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.util.zip.DataFormatException;
|
import java.util.zip.DataFormatException;
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ public class NativeVelocityCompressor implements VelocityCompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNative() {
|
public BufferPreference preferredBufferType() {
|
||||||
return true;
|
return BufferPreference.DIRECT_REQUIRED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.velocitypowered.natives.encryption;
|
package com.velocitypowered.natives.encryption;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.natives.util.BufferPreference;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@ -34,50 +35,25 @@ public class JavaVelocityCipher implements VelocityCipher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
public void process(ByteBuf source) {
|
||||||
ensureNotDisposed();
|
ensureNotDisposed();
|
||||||
|
Preconditions.checkArgument(source.hasArray(), "No source array");
|
||||||
|
|
||||||
int inBytes = source.readableBytes();
|
int inBytes = source.readableBytes();
|
||||||
byte[] asBytes = ByteBufUtil.getBytes(source);
|
int baseOffset = source.arrayOffset() + source.readerIndex();
|
||||||
|
|
||||||
int outputSize = cipher.getOutputSize(inBytes);
|
|
||||||
byte[] outBuf = new byte[outputSize];
|
|
||||||
cipher.update(asBytes, 0, inBytes, outBuf);
|
|
||||||
destination.writeBytes(outBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBuf process(ChannelHandlerContext ctx, ByteBuf source) throws ShortBufferException {
|
|
||||||
ensureNotDisposed();
|
|
||||||
|
|
||||||
int inBytes = source.readableBytes();
|
|
||||||
ByteBuf asHeapBuf = toHeap(source);
|
|
||||||
ByteBuf out = ctx.alloc().heapBuffer(cipher.getOutputSize(inBytes));
|
|
||||||
try {
|
try {
|
||||||
out.writerIndex(
|
cipher.update(source.array(), baseOffset, inBytes, source.array(), baseOffset);
|
||||||
cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset() + asHeapBuf.readerIndex(),
|
} catch (ShortBufferException ex) {
|
||||||
inBytes, out.array(), out.arrayOffset() + out.writerIndex()));
|
/* This _really_ shouldn't happen - AES CFB8 will work in place.
|
||||||
return out;
|
If you run into this, that means that for whatever reason the Java Runtime has determined
|
||||||
} catch (ShortBufferException e) {
|
that the output buffer needs more bytes than the input buffer. When we are working with
|
||||||
out.release();
|
AES-CFB8, the output size is equal to the input size. See the problem? */
|
||||||
throw e;
|
throw new AssertionError("Cipher update did not operate in place and requested a larger "
|
||||||
} finally {
|
+ "buffer than the source buffer");
|
||||||
asHeapBuf.release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ByteBuf toHeap(ByteBuf src) {
|
|
||||||
if (src.hasArray()) {
|
|
||||||
return src.retain();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy into a temporary heap buffer. We could use a local buffer, but Netty pools all buffers,
|
|
||||||
// so we'd lose more than we gain.
|
|
||||||
ByteBuf asHeapBuf = src.alloc().heapBuffer(src.readableBytes());
|
|
||||||
asHeapBuf.writeBytes(src);
|
|
||||||
return asHeapBuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
@ -88,7 +64,7 @@ public class JavaVelocityCipher implements VelocityCipher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNative() {
|
public BufferPreference preferredBufferType() {
|
||||||
return false;
|
return BufferPreference.HEAP_REQUIRED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.velocitypowered.natives.encryption;
|
package com.velocitypowered.natives.encryption;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.natives.util.BufferPreference;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
@ -32,40 +33,14 @@ public class NativeVelocityCipher implements VelocityCipher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
public void process(ByteBuf source) {
|
||||||
ensureNotDisposed();
|
ensureNotDisposed();
|
||||||
source.memoryAddress();
|
source.memoryAddress();
|
||||||
destination.memoryAddress();
|
|
||||||
|
|
||||||
// The exact amount we read in is also the amount we write out.
|
long base = source.memoryAddress() + source.readerIndex();
|
||||||
int len = source.readableBytes();
|
int len = source.readableBytes();
|
||||||
destination.ensureWritable(len);
|
|
||||||
|
|
||||||
impl.process(ctx, source.memoryAddress() + source.readerIndex(), len,
|
impl.process(ctx, base, len, base, encrypt);
|
||||||
destination.memoryAddress() + destination.writerIndex(), encrypt);
|
|
||||||
|
|
||||||
source.skipBytes(len);
|
|
||||||
destination.writerIndex(destination.writerIndex() + len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBuf process(ChannelHandlerContext ctx, ByteBuf source) throws ShortBufferException {
|
|
||||||
ensureNotDisposed();
|
|
||||||
source.memoryAddress(); // sanity check
|
|
||||||
|
|
||||||
int len = source.readableBytes();
|
|
||||||
ByteBuf out = ctx.alloc().directBuffer(len);
|
|
||||||
|
|
||||||
try {
|
|
||||||
impl.process(this.ctx, source.memoryAddress() + source.readerIndex(), len,
|
|
||||||
out.memoryAddress(), encrypt);
|
|
||||||
source.skipBytes(len);
|
|
||||||
out.writerIndex(len);
|
|
||||||
return out;
|
|
||||||
} catch (Exception e) {
|
|
||||||
out.release();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -81,7 +56,7 @@ public class NativeVelocityCipher implements VelocityCipher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNative() {
|
public BufferPreference preferredBufferType() {
|
||||||
return true;
|
return BufferPreference.DIRECT_REQUIRED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,5 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import javax.crypto.ShortBufferException;
|
import javax.crypto.ShortBufferException;
|
||||||
|
|
||||||
public interface VelocityCipher extends Disposable, Native {
|
public interface VelocityCipher extends Disposable, Native {
|
||||||
|
void process(ByteBuf source);
|
||||||
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
|
|
||||||
|
|
||||||
ByteBuf process(ChannelHandlerContext ctx, ByteBuf source) throws ShortBufferException;
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.velocitypowered.natives.util;
|
||||||
|
|
||||||
|
public enum BufferPreference {
|
||||||
|
/**
|
||||||
|
* A heap buffer is required.
|
||||||
|
*/
|
||||||
|
HEAP_REQUIRED,
|
||||||
|
/**
|
||||||
|
* A heap buffer is preferred (but not required).
|
||||||
|
*/
|
||||||
|
HEAP_PREFERRED,
|
||||||
|
/**
|
||||||
|
* A direct buffer is preferred (but not required).
|
||||||
|
*/
|
||||||
|
DIRECT_PREFERRED,
|
||||||
|
/**
|
||||||
|
* A direct buffer is required.
|
||||||
|
*/
|
||||||
|
DIRECT_REQUIRED
|
||||||
|
}
|
@ -20,28 +20,30 @@ public class MoreByteBufUtils {
|
|||||||
* @return a buffer compatible with the native
|
* @return a buffer compatible with the native
|
||||||
*/
|
*/
|
||||||
public static ByteBuf ensureCompatible(ByteBufAllocator alloc, Native nativeStuff, ByteBuf buf) {
|
public static ByteBuf ensureCompatible(ByteBufAllocator alloc, Native nativeStuff, ByteBuf buf) {
|
||||||
if (!nativeStuff.isNative() || buf.hasMemoryAddress()) {
|
if (isCompatible(nativeStuff, buf)) {
|
||||||
// Will always work in either case. JNI code demands a memory address, and if we have a Java
|
|
||||||
// fallback, it uses byte arrays in all cases.
|
|
||||||
return buf.retain();
|
return buf.retain();
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's not, so we must make a direct copy.
|
// It's not, so we must make a direct copy.
|
||||||
ByteBuf newBuf = alloc.directBuffer(buf.readableBytes());
|
ByteBuf newBuf = preferredBuffer(alloc, nativeStuff, buf.readableBytes());
|
||||||
newBuf.writeBytes(buf);
|
newBuf.writeBytes(buf);
|
||||||
return newBuf;
|
return newBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static boolean isCompatible(Native nativeStuff, ByteBuf buf) {
|
||||||
* Creates a {@link ByteBuf} that will have the best performance with the specified
|
BufferPreference preferred = nativeStuff.preferredBufferType();
|
||||||
* {@code nativeStuff}.
|
switch (preferred) {
|
||||||
*
|
case DIRECT_PREFERRED:
|
||||||
* @param alloc the {@link ByteBufAllocator} to use
|
case HEAP_PREFERRED:
|
||||||
* @param nativeStuff the native we are working with
|
// The native prefers this type, but doesn't strictly require we provide it.
|
||||||
* @return a buffer compatible with the native
|
return true;
|
||||||
*/
|
case DIRECT_REQUIRED:
|
||||||
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff) {
|
return buf.hasMemoryAddress();
|
||||||
return nativeStuff.isNative() ? alloc.directBuffer() : alloc.heapBuffer();
|
case HEAP_REQUIRED:
|
||||||
|
return buf.hasArray();
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Preferred buffer type unknown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,7 +57,15 @@ public class MoreByteBufUtils {
|
|||||||
*/
|
*/
|
||||||
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff,
|
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff,
|
||||||
int initialCapacity) {
|
int initialCapacity) {
|
||||||
return nativeStuff.isNative() ? alloc.directBuffer(initialCapacity) : alloc
|
switch (nativeStuff.preferredBufferType()) {
|
||||||
.heapBuffer(initialCapacity);
|
case HEAP_REQUIRED:
|
||||||
|
case HEAP_PREFERRED:
|
||||||
|
return alloc.heapBuffer(initialCapacity);
|
||||||
|
case DIRECT_PREFERRED:
|
||||||
|
case DIRECT_REQUIRED:
|
||||||
|
return alloc.directBuffer(initialCapacity);
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Preferred buffer type unknown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T get() {
|
public T get() {
|
||||||
return selected.object;
|
return selected.constructed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> Variant<T> getVariant(List<Variant<T>> variants) {
|
private static <T> Variant<T> getVariant(List<Variant<T>> variants) {
|
||||||
@ -38,9 +38,14 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
|
|||||||
private Status status;
|
private Status status;
|
||||||
private final Runnable setup;
|
private final Runnable setup;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final T object;
|
private final Supplier<T> object;
|
||||||
|
private T constructed;
|
||||||
|
|
||||||
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) {
|
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) {
|
||||||
|
this(possiblyAvailable, setup, name, () -> object);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, Supplier<T> object) {
|
||||||
this.status =
|
this.status =
|
||||||
possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
|
possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
|
||||||
this.setup = setup;
|
this.setup = setup;
|
||||||
@ -57,6 +62,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
|
|||||||
if (status == Status.POSSIBLY_AVAILABLE) {
|
if (status == Status.POSSIBLY_AVAILABLE) {
|
||||||
try {
|
try {
|
||||||
setup.run();
|
setup.run();
|
||||||
|
constructed = object.get();
|
||||||
status = Status.SETUP;
|
status = Status.SETUP;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
status = Status.SETUP_FAILURE;
|
status = Status.SETUP_FAILURE;
|
||||||
@ -64,7 +70,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return constructed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import java.util.function.BooleanSupplier;
|
|||||||
|
|
||||||
public class NativeConstraints {
|
public class NativeConstraints {
|
||||||
private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled");
|
private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled");
|
||||||
|
private static final boolean IS_AMD64;
|
||||||
private static final boolean CAN_GET_MEMORYADDRESS;
|
private static final boolean CAN_GET_MEMORYADDRESS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -15,19 +16,28 @@ public class NativeConstraints {
|
|||||||
} finally {
|
} finally {
|
||||||
test.release();
|
test.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String osArch = System.getProperty("os.arch", "");
|
||||||
|
// HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere
|
||||||
|
// give amd64.
|
||||||
|
IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64");
|
||||||
}
|
}
|
||||||
|
|
||||||
static final BooleanSupplier MACOS = () -> {
|
static final BooleanSupplier MACOS = () -> {
|
||||||
return NATIVES_ENABLED
|
return NATIVES_ENABLED
|
||||||
&& CAN_GET_MEMORYADDRESS
|
&& CAN_GET_MEMORYADDRESS
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
||||||
&& System.getProperty("os.arch", "").equals("x86_64");
|
&& IS_AMD64;
|
||||||
};
|
};
|
||||||
|
|
||||||
static final BooleanSupplier LINUX = () -> {
|
static final BooleanSupplier LINUX = () -> {
|
||||||
return NATIVES_ENABLED
|
return NATIVES_ENABLED
|
||||||
&& CAN_GET_MEMORYADDRESS
|
&& CAN_GET_MEMORYADDRESS
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
||||||
&& System.getProperty("os.arch", "").equals("amd64");
|
&& IS_AMD64;
|
||||||
|
};
|
||||||
|
|
||||||
|
static final BooleanSupplier JAVA_11 = () -> {
|
||||||
|
return Double.parseDouble(System.getProperty("java.specification.version")) >= 11;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package com.velocitypowered.natives.util;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.velocitypowered.natives.NativeSetupException;
|
import com.velocitypowered.natives.NativeSetupException;
|
||||||
|
import com.velocitypowered.natives.compression.Java11VelocityCompressor;
|
||||||
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
|
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
|
||||||
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
|
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
|
||||||
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
|
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
|
||||||
@ -52,6 +53,8 @@ public class Natives {
|
|||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX,
|
||||||
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
|
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
|
||||||
NativeVelocityCompressor.FACTORY),
|
NativeVelocityCompressor.FACTORY),
|
||||||
|
new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> {
|
||||||
|
}, "Java 11", () -> Java11VelocityCompressor.FACTORY),
|
||||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
||||||
}, "Java", JavaVelocityCompressor.FACTORY)
|
}, "Java", JavaVelocityCompressor.FACTORY)
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||||||
import static org.junit.jupiter.api.condition.OS.LINUX;
|
import static org.junit.jupiter.api.condition.OS.LINUX;
|
||||||
import static org.junit.jupiter.api.condition.OS.MAC;
|
import static org.junit.jupiter.api.condition.OS.MAC;
|
||||||
|
|
||||||
|
import com.velocitypowered.natives.util.BufferPreference;
|
||||||
import com.velocitypowered.natives.util.Natives;
|
import com.velocitypowered.natives.util.Natives;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
@ -17,7 +18,9 @@ import java.util.zip.DataFormatException;
|
|||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnJre;
|
||||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||||
|
import org.junit.jupiter.api.condition.JRE;
|
||||||
|
|
||||||
class VelocityCompressorTest {
|
class VelocityCompressorTest {
|
||||||
|
|
||||||
@ -39,7 +42,7 @@ class VelocityCompressorTest {
|
|||||||
@EnabledOnOs({MAC, LINUX})
|
@EnabledOnOs({MAC, LINUX})
|
||||||
void nativeIntegrityCheck() throws DataFormatException {
|
void nativeIntegrityCheck() throws DataFormatException {
|
||||||
VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION);
|
VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION);
|
||||||
if (compressor instanceof JavaVelocityCompressor) {
|
if (compressor.preferredBufferType() != BufferPreference.DIRECT_REQUIRED) {
|
||||||
compressor.dispose();
|
compressor.dispose();
|
||||||
fail("Loaded regular compressor");
|
fail("Loaded regular compressor");
|
||||||
}
|
}
|
||||||
@ -60,6 +63,22 @@ class VelocityCompressorTest {
|
|||||||
check(compressor, () -> Unpooled.buffer(TEST_DATA.length + 32));
|
check(compressor, () -> Unpooled.buffer(TEST_DATA.length + 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnJre(JRE.JAVA_11)
|
||||||
|
void java11IntegrityCheckDirect() throws DataFormatException {
|
||||||
|
VelocityCompressor compressor = Java11VelocityCompressor.FACTORY
|
||||||
|
.create(Deflater.DEFAULT_COMPRESSION);
|
||||||
|
check(compressor, () -> Unpooled.directBuffer(TEST_DATA.length + 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnJre(JRE.JAVA_11)
|
||||||
|
void java11IntegrityCheckHeap() throws DataFormatException {
|
||||||
|
VelocityCompressor compressor = Java11VelocityCompressor.FACTORY
|
||||||
|
.create(Deflater.DEFAULT_COMPRESSION);
|
||||||
|
check(compressor, () -> Unpooled.buffer(TEST_DATA.length + 32));
|
||||||
|
}
|
||||||
|
|
||||||
private void check(VelocityCompressor compressor, Supplier<ByteBuf> bufSupplier)
|
private void check(VelocityCompressor compressor, Supplier<ByteBuf> bufSupplier)
|
||||||
throws DataFormatException {
|
throws DataFormatException {
|
||||||
ByteBuf source = bufSupplier.get();
|
ByteBuf source = bufSupplier.get();
|
||||||
|
@ -56,20 +56,18 @@ class VelocityCipherTest {
|
|||||||
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(AES_KEY, "AES"));
|
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(AES_KEY, "AES"));
|
||||||
|
|
||||||
ByteBuf source = bufSupplier.get();
|
ByteBuf source = bufSupplier.get();
|
||||||
ByteBuf dest = bufSupplier.get();
|
|
||||||
ByteBuf decryptionBuf = bufSupplier.get();
|
|
||||||
|
|
||||||
source.writeBytes(TEST_DATA);
|
source.writeBytes(TEST_DATA);
|
||||||
|
|
||||||
|
ByteBuf workingBuf = source.copy();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
encrypt.process(source, dest);
|
encrypt.process(workingBuf);
|
||||||
decrypt.process(dest, decryptionBuf);
|
decrypt.process(workingBuf);
|
||||||
source.readerIndex(0);
|
assertTrue(ByteBufUtil.equals(source, workingBuf));
|
||||||
assertTrue(ByteBufUtil.equals(source, decryptionBuf));
|
|
||||||
} finally {
|
} finally {
|
||||||
source.release();
|
source.release();
|
||||||
dest.release();
|
workingBuf.release();
|
||||||
decryptionBuf.release();
|
|
||||||
decrypt.dispose();
|
decrypt.dispose();
|
||||||
encrypt.dispose();
|
encrypt.dispose();
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,8 @@ dependencies {
|
|||||||
compile "io.netty:netty-handler:${nettyVersion}"
|
compile "io.netty:netty-handler:${nettyVersion}"
|
||||||
compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
|
compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
|
||||||
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
|
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
|
||||||
|
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}"
|
||||||
|
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
|
||||||
compile "io.netty:netty-resolver-dns:${nettyVersion}"
|
compile "io.netty:netty-resolver-dns:${nettyVersion}"
|
||||||
|
|
||||||
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
|
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
|
||||||
@ -65,7 +67,7 @@ dependencies {
|
|||||||
|
|
||||||
compile 'com.mojang:brigadier:1.0.15'
|
compile 'com.mojang:brigadier:1.0.15'
|
||||||
|
|
||||||
compile 'org.asynchttpclient:async-http-client:2.10.1'
|
compile 'org.asynchttpclient:async-http-client:2.10.4'
|
||||||
|
|
||||||
compile 'com.spotify:completable-futures:0.3.2'
|
compile 'com.spotify:completable-futures:0.3.2'
|
||||||
|
|
||||||
|
@ -72,7 +72,10 @@ public class VelocityCommand implements Command {
|
|||||||
@Override
|
@Override
|
||||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||||
if (currentArgs.length == 0) {
|
if (currentArgs.length == 0) {
|
||||||
return ImmutableList.copyOf(subcommands.keySet());
|
return subcommands.entrySet().stream()
|
||||||
|
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.collect(ImmutableList.toImmutableList());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentArgs.length == 1) {
|
if (currentArgs.length == 1) {
|
||||||
@ -81,7 +84,7 @@ public class VelocityCommand implements Command {
|
|||||||
currentArgs[0].length()))
|
currentArgs[0].length()))
|
||||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||||
.map(Map.Entry::getKey)
|
.map(Map.Entry::getKey)
|
||||||
.collect(Collectors.toList());
|
.collect(ImmutableList.toImmutableList());
|
||||||
}
|
}
|
||||||
|
|
||||||
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));
|
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));
|
||||||
|
@ -100,7 +100,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
" configuration is used if no servers could be contacted."
|
" configuration is used if no servers could be contacted."
|
||||||
})
|
})
|
||||||
@ConfigKey("ping-passthrough")
|
@ConfigKey("ping-passthrough")
|
||||||
private PingPassthroughMode pingPassthrough;
|
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||||
|
|
||||||
@Table("[servers]")
|
@Table("[servers]")
|
||||||
private final Servers servers;
|
private final Servers servers;
|
||||||
@ -192,12 +192,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (servers.getServers().isEmpty()) {
|
if (servers.getServers().isEmpty()) {
|
||||||
logger.error("You have no servers configured. :(");
|
logger.warn("You don't have any servers configured.");
|
||||||
valid = false;
|
|
||||||
} else {
|
|
||||||
if (servers.getAttemptConnectionOrder().isEmpty()) {
|
|
||||||
logger.error("No fallback servers are configured!");
|
|
||||||
valid = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
|
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
|
||||||
@ -230,7 +225,6 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
getMotdComponent();
|
getMotdComponent();
|
||||||
|
@ -34,14 +34,4 @@ public interface ConnectionType {
|
|||||||
*/
|
*/
|
||||||
GameProfile addGameProfileTokensIfRequired(GameProfile original,
|
GameProfile addGameProfileTokensIfRequired(GameProfile original,
|
||||||
PlayerInfoForwarding forwardingType);
|
PlayerInfoForwarding forwardingType);
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests whether the hostname is the handshake packet is valid.
|
|
||||||
*
|
|
||||||
* @param address The address to check
|
|
||||||
* @return true if valid.
|
|
||||||
*/
|
|
||||||
default boolean checkServerAddressIsValid(String address) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,8 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
|
|||||||
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
|
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
|
||||||
smc.setAutoReading(false);
|
smc.setAutoReading(false);
|
||||||
server.getEventManager()
|
server.getEventManager()
|
||||||
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer()))
|
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer(),
|
||||||
|
existingConnection != null ? existingConnection.getServer() : null))
|
||||||
.whenCompleteAsync((x, error) -> {
|
.whenCompleteAsync((x, error) -> {
|
||||||
// Strap on the ClientPlaySessionHandler if required.
|
// Strap on the ClientPlaySessionHandler if required.
|
||||||
ClientPlaySessionHandler playHandler;
|
ClientPlaySessionHandler playHandler;
|
||||||
|
@ -304,26 +304,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
// Clear tab list to avoid duplicate entries
|
// Clear tab list to avoid duplicate entries
|
||||||
player.getTabList().clearAll();
|
player.getTabList().clearAll();
|
||||||
|
|
||||||
// In order to handle switching to another server, you will need to send three packets:
|
// In order to handle switching to another server, you will need to send two packets:
|
||||||
//
|
//
|
||||||
// - The join game packet from the backend server
|
// - The join game packet from the backend server, with a different dimension
|
||||||
// - A respawn packet with a different dimension
|
// - A respawn with the correct dimension
|
||||||
// - Another respawn with the correct dimension
|
|
||||||
//
|
|
||||||
// The two respawns with different dimensions are required, otherwise the client gets
|
|
||||||
// confused.
|
|
||||||
//
|
//
|
||||||
// Most notably, by having the client accept the join game packet, we can work around the need
|
// Most notably, by having the client accept the join game packet, we can work around the need
|
||||||
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
|
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
|
||||||
// improving compatibility with mods.
|
// improving compatibility with mods.
|
||||||
|
int realDim = joinGame.getDimension();
|
||||||
|
joinGame.setDimension(getFakeTemporaryDimensionId(realDim));
|
||||||
player.getConnection().delayedWrite(joinGame);
|
player.getConnection().delayedWrite(joinGame);
|
||||||
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
|
|
||||||
player.getConnection().delayedWrite(
|
player.getConnection().delayedWrite(
|
||||||
new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(),
|
new Respawn(realDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
|
||||||
joinGame.getLevelType()));
|
joinGame.getGamemode(), joinGame.getLevelType()));
|
||||||
player.getConnection().delayedWrite(
|
|
||||||
new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(),
|
|
||||||
joinGame.getLevelType()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
|
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
|
||||||
@ -360,11 +354,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
destination.completeJoin();
|
destination.completeJoin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getFakeTemporaryDimensionId(int dim) {
|
||||||
|
return dim == 0 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
public List<UUID> getServerBossBars() {
|
public List<UUID> getServerBossBars() {
|
||||||
return serverBossBars;
|
return serverBossBars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean handleCommandTabComplete(TabCompleteRequest packet) {
|
private boolean handleCommandTabComplete(TabCompleteRequest packet) {
|
||||||
// In 1.13+, we need to do additional work for the richer suggestions available.
|
// In 1.13+, we need to do additional work for the richer suggestions available.
|
||||||
String command = packet.getCommand().substring(1);
|
String command = packet.getCommand().substring(1);
|
||||||
@ -389,31 +386,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Offer> offers = new ArrayList<>();
|
List<Offer> offers = new ArrayList<>();
|
||||||
int longestLength = 0;
|
|
||||||
for (String suggestion : suggestions) {
|
for (String suggestion : suggestions) {
|
||||||
offers.add(new Offer(suggestion));
|
offers.add(new Offer(suggestion));
|
||||||
if (suggestion.length() > longestLength) {
|
|
||||||
longestLength = suggestion.length();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TabCompleteResponse resp = new TabCompleteResponse();
|
|
||||||
resp.setTransactionId(packet.getTransactionId());
|
|
||||||
|
|
||||||
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
|
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
|
||||||
int length;
|
if (startPos > 0) {
|
||||||
if (startPos == 0) {
|
TabCompleteResponse resp = new TabCompleteResponse();
|
||||||
startPos = packet.getCommand().length() + 1;
|
resp.setTransactionId(packet.getTransactionId());
|
||||||
length = longestLength;
|
|
||||||
} else {
|
|
||||||
length = packet.getCommand().length() - startPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.setStart(startPos);
|
resp.setStart(startPos);
|
||||||
resp.setLength(length);
|
resp.setLength(packet.getCommand().length() - startPos);
|
||||||
resp.getOffers().addAll(offers);
|
resp.getOffers().addAll(offers);
|
||||||
|
|
||||||
player.getConnection().write(resp);
|
player.getConnection().write(resp);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,17 +425,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectedServer == null) {
|
boolean kickedFromCurrent = connectedServer == null || connectedServer.getServer().equals(rs);
|
||||||
Optional<RegisteredServer> nextServer = getNextServerToTry(rs);
|
|
||||||
if (nextServer.isPresent()) {
|
|
||||||
// There can't be any connection in flight now.
|
|
||||||
resetInFlightConnection();
|
|
||||||
createConnectionRequest(nextServer.get()).fireAndForget();
|
|
||||||
} else {
|
|
||||||
disconnect(friendlyReason);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
boolean kickedFromCurrent = connectedServer.getServer().equals(rs);
|
|
||||||
ServerKickResult result;
|
ServerKickResult result;
|
||||||
if (kickedFromCurrent) {
|
if (kickedFromCurrent) {
|
||||||
Optional<RegisteredServer> next = getNextServerToTry(rs);
|
Optional<RegisteredServer> next = getNextServerToTry(rs);
|
||||||
@ -452,7 +442,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
!kickedFromCurrent, result);
|
!kickedFromCurrent, result);
|
||||||
handleKickEvent(originalEvent, friendlyReason);
|
handleKickEvent(originalEvent, friendlyReason);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) {
|
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) {
|
||||||
server.getEventManager().fire(originalEvent)
|
server.getEventManager().fire(originalEvent)
|
||||||
@ -471,7 +460,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
if (newResult == null || !newResult) {
|
if (newResult == null || !newResult) {
|
||||||
disconnect(friendlyReason);
|
disconnect(friendlyReason);
|
||||||
} else {
|
} else {
|
||||||
|
if (res.getMessage() == null) {
|
||||||
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason));
|
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason));
|
||||||
|
} else {
|
||||||
|
sendMessage(res.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, connection.eventLoop());
|
}, connection.eventLoop());
|
||||||
} else if (event.getResult() instanceof Notify) {
|
} else if (event.getResult() instanceof Notify) {
|
||||||
|
@ -26,9 +26,14 @@ import java.util.Optional;
|
|||||||
import net.kyori.text.TextComponent;
|
import net.kyori.text.TextComponent;
|
||||||
import net.kyori.text.TranslatableComponent;
|
import net.kyori.text.TranslatableComponent;
|
||||||
import net.kyori.text.format.TextColor;
|
import net.kyori.text.format.TextColor;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(HandshakeSessionHandler.class);
|
||||||
|
|
||||||
private final MinecraftConnection connection;
|
private final MinecraftConnection connection;
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
|
|
||||||
@ -58,39 +63,57 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
|||||||
public boolean handle(Handshake handshake) {
|
public boolean handle(Handshake handshake) {
|
||||||
InitialInboundConnection ic = new InitialInboundConnection(connection,
|
InitialInboundConnection ic = new InitialInboundConnection(connection,
|
||||||
cleanVhost(handshake.getServerAddress()), handshake);
|
cleanVhost(handshake.getServerAddress()), handshake);
|
||||||
|
StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
|
||||||
|
if (nextState == null) {
|
||||||
|
LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus());
|
||||||
|
connection.close();
|
||||||
|
} else {
|
||||||
|
connection.setState(nextState);
|
||||||
|
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||||
connection.setAssociation(ic);
|
connection.setAssociation(ic);
|
||||||
switch (handshake.getNextStatus()) {
|
|
||||||
case StateRegistry.STATUS_ID:
|
|
||||||
connection.setState(StateRegistry.STATUS);
|
|
||||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
|
||||||
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
|
||||||
return true;
|
|
||||||
case StateRegistry.LOGIN_ID:
|
|
||||||
connection.setState(StateRegistry.LOGIN);
|
|
||||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
|
||||||
|
|
||||||
|
switch (nextState) {
|
||||||
|
case STATUS:
|
||||||
|
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
||||||
|
break;
|
||||||
|
case LOGIN:
|
||||||
|
this.handleLogin(handshake, ic);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// If you get this, it's a bug in Velocity.
|
||||||
|
throw new AssertionError("getStateForProtocol provided invalid state!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable StateRegistry getStateForProtocol(int status) {
|
||||||
|
switch (status) {
|
||||||
|
case StateRegistry.STATUS_ID:
|
||||||
|
return StateRegistry.STATUS;
|
||||||
|
case StateRegistry.LOGIN_ID:
|
||||||
|
return StateRegistry.LOGIN;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLogin(Handshake handshake, InitialInboundConnection ic) {
|
||||||
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
|
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
|
||||||
connection.closeWith(Disconnect
|
connection.closeWith(Disconnect
|
||||||
.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
|
.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||||
if (!server.getIpAttemptLimiter().attempt(address)) {
|
if (!server.getIpAttemptLimiter().attempt(address)) {
|
||||||
connection.closeWith(
|
connection.closeWith(
|
||||||
Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
|
Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionType type = checkForForge(handshake);
|
connection.setType(getHandshakeConnectionType(handshake));
|
||||||
connection.setType(type);
|
|
||||||
|
|
||||||
// Make sure legacy forwarding is not in use on this connection.
|
|
||||||
if (!type.checkServerAddressIsValid(handshake.getServerAddress())) {
|
|
||||||
connection.closeWith(Disconnect
|
|
||||||
.create(TextComponent.of("Running Velocity behind Velocity is unsupported.")));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2
|
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2
|
||||||
// and lower, otherwise IP information will never get forwarded.
|
// and lower, otherwise IP information will never get forwarded.
|
||||||
@ -98,18 +121,14 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
|||||||
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
||||||
connection.closeWith(Disconnect
|
connection.closeWith(Disconnect
|
||||||
.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
|
.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
|
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
|
||||||
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
|
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConnectionType checkForForge(Handshake handshake) {
|
private ConnectionType getHandshakeConnectionType(Handshake handshake) {
|
||||||
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13).
|
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13).
|
||||||
if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
|
if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
|
||||||
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
||||||
@ -119,8 +138,8 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
|||||||
// forge handshake attempts. Also sends a reset handshake packet on every transition.
|
// forge handshake attempts. Also sends a reset handshake packet on every transition.
|
||||||
return ConnectionTypes.UNDETERMINED_17;
|
return ConnectionTypes.UNDETERMINED_17;
|
||||||
} else {
|
} else {
|
||||||
// For later: See if we can determine Forge 1.13+ here, else this will need to be UNDETERMINED
|
// Note for future implementation: Forge 1.13+ identifies itself using a slightly different
|
||||||
// until later in the cycle (most likely determinable during the LOGIN phase)
|
// hostname token.
|
||||||
return ConnectionTypes.VANILLA;
|
return ConnectionTypes.VANILLA;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
|
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
|
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||||
import static com.velocitypowered.proxy.VelocityServer.GSON;
|
import static com.velocitypowered.proxy.VelocityServer.GSON;
|
||||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||||
import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL;
|
|
||||||
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
|
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
|
||||||
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
|
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
|
||||||
import static org.asynchttpclient.Dsl.asyncHttpClient;
|
import static org.asynchttpclient.Dsl.asyncHttpClient;
|
||||||
import static org.asynchttpclient.Dsl.config;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.net.UrlEscapers;
|
|
||||||
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.connection.PreLoginEvent;
|
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||||
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.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
@ -29,17 +26,12 @@ import com.velocitypowered.proxy.protocol.StateRegistry;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||||
import com.velocitypowered.proxy.util.VelocityMessages;
|
import com.velocitypowered.proxy.util.VelocityMessages;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -50,7 +42,6 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||||||
import net.kyori.text.Component;
|
import net.kyori.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;
|
||||||
import org.asynchttpclient.Dsl;
|
|
||||||
import org.asynchttpclient.ListenableFuture;
|
import org.asynchttpclient.ListenableFuture;
|
||||||
import org.asynchttpclient.Response;
|
import org.asynchttpclient.Response;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@ -66,7 +57,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
private final InitialInboundConnection inbound;
|
private final InitialInboundConnection inbound;
|
||||||
private @MonotonicNonNull ServerLogin login;
|
private @MonotonicNonNull ServerLogin login;
|
||||||
private byte[] verify = EMPTY_BYTE_ARRAY;
|
private byte[] verify = EMPTY_BYTE_ARRAY;
|
||||||
private int playerInfoId;
|
|
||||||
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
||||||
|
|
||||||
LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
|
LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
|
||||||
@ -79,29 +69,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public boolean handle(ServerLogin packet) {
|
public boolean handle(ServerLogin packet) {
|
||||||
this.login = packet;
|
this.login = packet;
|
||||||
if (mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0) {
|
|
||||||
// To make sure the connecting client isn't Velocity, send a plugin message that Velocity will
|
|
||||||
// recognize and respond to.
|
|
||||||
playerInfoId = ThreadLocalRandom.current().nextInt();
|
|
||||||
mcConnection.write(new LoginPluginMessage(playerInfoId, VELOCITY_IP_FORWARDING_CHANNEL,
|
|
||||||
Unpooled.EMPTY_BUFFER));
|
|
||||||
} else {
|
|
||||||
beginPreLogin();
|
beginPreLogin();
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handle(LoginPluginResponse packet) {
|
|
||||||
if (packet.getId() == playerInfoId) {
|
|
||||||
if (packet.isSuccess()) {
|
|
||||||
// Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening.
|
|
||||||
inbound.disconnect(VelocityMessages.NO_PROXY_BEHIND_PROXY);
|
|
||||||
} else {
|
|
||||||
// Proceed with the regular login process.
|
|
||||||
beginPreLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,12 +222,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void finishLogin(ConnectedPlayer player) {
|
private void finishLogin(ConnectedPlayer player) {
|
||||||
Optional<RegisteredServer> toTry = player.getNextServerToTry();
|
|
||||||
if (!toTry.isPresent()) {
|
|
||||||
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int threshold = server.getConfiguration().getCompressionThreshold();
|
int threshold = server.getConfiguration().getCompressionThreshold();
|
||||||
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||||
mcConnection.write(new SetCompression(threshold));
|
mcConnection.write(new SetCompression(threshold));
|
||||||
@ -292,11 +254,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
|
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||||
server.getEventManager().fire(new PostLoginEvent(player))
|
server.getEventManager().fire(new PostLoginEvent(player))
|
||||||
.thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
|
.thenRun(() -> connectToInitialServer(player));
|
||||||
}
|
}
|
||||||
}, mcConnection.eventLoop());
|
}, mcConnection.eventLoop());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void connectToInitialServer(ConnectedPlayer player) {
|
||||||
|
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
|
||||||
|
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
|
||||||
|
initialFromConfig.orElse(null));
|
||||||
|
|
||||||
|
server.getEventManager().fire(event)
|
||||||
|
.thenRunAsync(() -> {
|
||||||
|
Optional<RegisteredServer> toTry = event.getInitialServer();
|
||||||
|
if (!toTry.isPresent()) {
|
||||||
|
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||||
|
}, mcConnection.eventLoop());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleUnknown(ByteBuf buf) {
|
public void handleUnknown(ByteBuf buf) {
|
||||||
mcConnection.close();
|
mcConnection.close();
|
||||||
|
@ -6,7 +6,7 @@ package com.velocitypowered.proxy.connection.forge.legacy;
|
|||||||
public class LegacyForgeConstants {
|
public class LegacyForgeConstants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clients attempting to connect to 1.8+ Forge servers will have
|
* Clients attempting to connect to 1.8-1.12.2 Forge servers will have
|
||||||
* this token appended to the hostname in the initial handshake
|
* this token appended to the hostname in the initial handshake
|
||||||
* packet.
|
* packet.
|
||||||
*/
|
*/
|
||||||
|
@ -7,6 +7,11 @@ import io.netty.channel.epoll.EpollDatagramChannel;
|
|||||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||||
import io.netty.channel.epoll.EpollSocketChannel;
|
import io.netty.channel.epoll.EpollSocketChannel;
|
||||||
|
import io.netty.channel.kqueue.KQueue;
|
||||||
|
import io.netty.channel.kqueue.KQueueDatagramChannel;
|
||||||
|
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||||
|
import io.netty.channel.kqueue.KQueueServerSocketChannel;
|
||||||
|
import io.netty.channel.kqueue.KQueueSocketChannel;
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.DatagramChannel;
|
import io.netty.channel.socket.DatagramChannel;
|
||||||
import io.netty.channel.socket.ServerSocketChannel;
|
import io.netty.channel.socket.ServerSocketChannel;
|
||||||
@ -22,7 +27,11 @@ enum TransportType {
|
|||||||
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
||||||
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
|
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
|
||||||
EpollDatagramChannel.class,
|
EpollDatagramChannel.class,
|
||||||
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)));
|
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))),
|
||||||
|
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
|
||||||
|
KQueueDatagramChannel.class,
|
||||||
|
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
|
||||||
|
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
||||||
@ -62,6 +71,8 @@ enum TransportType {
|
|||||||
|
|
||||||
if (Epoll.isAvailable()) {
|
if (Epoll.isAvailable()) {
|
||||||
return EPOLL;
|
return EPOLL;
|
||||||
|
} else if (KQueue.isAvailable()) {
|
||||||
|
return KQUEUE;
|
||||||
} else {
|
} else {
|
||||||
return NIO;
|
return NIO;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.velocitypowered.proxy.plugin;
|
|||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
import com.velocitypowered.api.plugin.PluginContainer;
|
import com.velocitypowered.api.plugin.PluginContainer;
|
||||||
import com.velocitypowered.api.plugin.PluginDescription;
|
import com.velocitypowered.api.plugin.PluginDescription;
|
||||||
import com.velocitypowered.api.plugin.PluginManager;
|
import com.velocitypowered.api.plugin.PluginManager;
|
||||||
@ -101,6 +102,8 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("Loaded plugin {} {} by {}", plugin.getId(), plugin.getVersion()
|
||||||
|
.orElse("<UNKNOWN>"), Joiner.on(", ").join(plugin.getAuthors()));
|
||||||
registerPlugin(pluginObject);
|
registerPlugin(pluginObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12;
|
|||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14;
|
||||||
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_15;
|
||||||
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;
|
||||||
@ -120,51 +121,61 @@ public enum StateRegistry {
|
|||||||
map(0x1F, MINECRAFT_1_14, false));
|
map(0x1F, MINECRAFT_1_14, false));
|
||||||
|
|
||||||
clientbound.register(BossBar.class, BossBar::new,
|
clientbound.register(BossBar.class, BossBar::new,
|
||||||
map(0x0C, MINECRAFT_1_9, false));
|
map(0x0C, MINECRAFT_1_9, false),
|
||||||
|
map(0x0D, MINECRAFT_1_15, false));
|
||||||
clientbound.register(Chat.class, Chat::new,
|
clientbound.register(Chat.class, Chat::new,
|
||||||
map(0x02, MINECRAFT_1_7_2, true),
|
map(0x02, MINECRAFT_1_7_2, true),
|
||||||
map(0x0F, MINECRAFT_1_9, true),
|
map(0x0F, MINECRAFT_1_9, true),
|
||||||
map(0x0E, MINECRAFT_1_13, true));
|
map(0x0E, MINECRAFT_1_13, true),
|
||||||
|
map(0x0F, MINECRAFT_1_15, true));
|
||||||
clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new,
|
clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new,
|
||||||
map(0x3A, MINECRAFT_1_7_2, false),
|
map(0x3A, MINECRAFT_1_7_2, false),
|
||||||
map(0x0E, MINECRAFT_1_9, false),
|
map(0x0E, MINECRAFT_1_9, false),
|
||||||
map(0x10, MINECRAFT_1_13, false));
|
map(0x10, MINECRAFT_1_13, false),
|
||||||
|
map(0x11, MINECRAFT_1_15, false));
|
||||||
clientbound.register(AvailableCommands.class, AvailableCommands::new,
|
clientbound.register(AvailableCommands.class, AvailableCommands::new,
|
||||||
map(0x11, MINECRAFT_1_13, false));
|
map(0x11, MINECRAFT_1_13, false),
|
||||||
|
map(0x12, MINECRAFT_1_15, false));
|
||||||
clientbound.register(PluginMessage.class, PluginMessage::new,
|
clientbound.register(PluginMessage.class, PluginMessage::new,
|
||||||
map(0x3F, MINECRAFT_1_7_2, false),
|
map(0x3F, MINECRAFT_1_7_2, false),
|
||||||
map(0x18, MINECRAFT_1_9, false),
|
map(0x18, MINECRAFT_1_9, false),
|
||||||
map(0x19, MINECRAFT_1_13, false),
|
map(0x19, MINECRAFT_1_13, false),
|
||||||
map(0x18, MINECRAFT_1_14, false));
|
map(0x18, MINECRAFT_1_14, false),
|
||||||
|
map(0x19, MINECRAFT_1_15, false));
|
||||||
clientbound.register(Disconnect.class, Disconnect::new,
|
clientbound.register(Disconnect.class, Disconnect::new,
|
||||||
map(0x40, MINECRAFT_1_7_2, false),
|
map(0x40, MINECRAFT_1_7_2, false),
|
||||||
map(0x1A, MINECRAFT_1_9, false),
|
map(0x1A, MINECRAFT_1_9, false),
|
||||||
map(0x1B, MINECRAFT_1_13, false),
|
map(0x1B, MINECRAFT_1_13, false),
|
||||||
map(0x1A, MINECRAFT_1_14, false));
|
map(0x1A, MINECRAFT_1_14, false),
|
||||||
|
map(0x1B, MINECRAFT_1_15, false));
|
||||||
clientbound.register(KeepAlive.class, KeepAlive::new,
|
clientbound.register(KeepAlive.class, KeepAlive::new,
|
||||||
map(0x00, MINECRAFT_1_7_2, false),
|
map(0x00, MINECRAFT_1_7_2, false),
|
||||||
map(0x1F, MINECRAFT_1_9, false),
|
map(0x1F, MINECRAFT_1_9, false),
|
||||||
map(0x21, MINECRAFT_1_13, false),
|
map(0x21, MINECRAFT_1_13, false),
|
||||||
map(0x20, MINECRAFT_1_14, false));
|
map(0x20, MINECRAFT_1_14, false),
|
||||||
|
map(0x21, MINECRAFT_1_15, false));
|
||||||
clientbound.register(JoinGame.class, JoinGame::new,
|
clientbound.register(JoinGame.class, JoinGame::new,
|
||||||
map(0x01, MINECRAFT_1_7_2, false),
|
map(0x01, MINECRAFT_1_7_2, false),
|
||||||
map(0x23, MINECRAFT_1_9, false),
|
map(0x23, MINECRAFT_1_9, false),
|
||||||
map(0x25, MINECRAFT_1_13, false),
|
map(0x25, MINECRAFT_1_13, false),
|
||||||
map(0x25, MINECRAFT_1_14, false));
|
map(0x25, MINECRAFT_1_14, false),
|
||||||
|
map(0x26, MINECRAFT_1_15, false));
|
||||||
clientbound.register(Respawn.class, Respawn::new,
|
clientbound.register(Respawn.class, Respawn::new,
|
||||||
map(0x07, MINECRAFT_1_7_2, true),
|
map(0x07, MINECRAFT_1_7_2, true),
|
||||||
map(0x33, MINECRAFT_1_9, true),
|
map(0x33, MINECRAFT_1_9, true),
|
||||||
map(0x34, MINECRAFT_1_12, true),
|
map(0x34, MINECRAFT_1_12, true),
|
||||||
map(0x35, MINECRAFT_1_12_1, true),
|
map(0x35, MINECRAFT_1_12_1, true),
|
||||||
map(0x38, MINECRAFT_1_13, true),
|
map(0x38, MINECRAFT_1_13, true),
|
||||||
map(0x3A, MINECRAFT_1_14, true));
|
map(0x3A, MINECRAFT_1_14, true),
|
||||||
|
map(0x3B, MINECRAFT_1_15, true));
|
||||||
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
|
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
|
||||||
map(0x48, MINECRAFT_1_8, true),
|
map(0x48, MINECRAFT_1_8, true),
|
||||||
map(0x32, MINECRAFT_1_9, true),
|
map(0x32, MINECRAFT_1_9, true),
|
||||||
map(0x33, MINECRAFT_1_12, true),
|
map(0x33, MINECRAFT_1_12, true),
|
||||||
map(0x34, MINECRAFT_1_12_1, true),
|
map(0x34, MINECRAFT_1_12_1, true),
|
||||||
map(0x37, MINECRAFT_1_13, true),
|
map(0x37, MINECRAFT_1_13, true),
|
||||||
map(0x39, MINECRAFT_1_14, true));
|
map(0x39, MINECRAFT_1_14, true),
|
||||||
|
map(0x3A, MINECRAFT_1_15, true));
|
||||||
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
|
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
|
||||||
map(0x47, MINECRAFT_1_8, true),
|
map(0x47, MINECRAFT_1_8, true),
|
||||||
map(0x48, MINECRAFT_1_9, true),
|
map(0x48, MINECRAFT_1_9, true),
|
||||||
@ -172,20 +183,23 @@ public enum StateRegistry {
|
|||||||
map(0x49, MINECRAFT_1_12, true),
|
map(0x49, MINECRAFT_1_12, true),
|
||||||
map(0x4A, MINECRAFT_1_12_1, true),
|
map(0x4A, MINECRAFT_1_12_1, true),
|
||||||
map(0x4E, MINECRAFT_1_13, true),
|
map(0x4E, MINECRAFT_1_13, true),
|
||||||
map(0x53, MINECRAFT_1_14, true));
|
map(0x53, MINECRAFT_1_14, true),
|
||||||
|
map(0x54, MINECRAFT_1_15, true));
|
||||||
clientbound.register(TitlePacket.class, TitlePacket::new,
|
clientbound.register(TitlePacket.class, TitlePacket::new,
|
||||||
map(0x45, MINECRAFT_1_8, true),
|
map(0x45, MINECRAFT_1_8, true),
|
||||||
map(0x45, MINECRAFT_1_9, true),
|
map(0x45, MINECRAFT_1_9, true),
|
||||||
map(0x47, MINECRAFT_1_12, true),
|
map(0x47, MINECRAFT_1_12, true),
|
||||||
map(0x48, MINECRAFT_1_12_1, true),
|
map(0x48, MINECRAFT_1_12_1, true),
|
||||||
map(0x4B, MINECRAFT_1_13, true),
|
map(0x4B, MINECRAFT_1_13, true),
|
||||||
map(0x4F, MINECRAFT_1_14, true));
|
map(0x4F, MINECRAFT_1_14, true),
|
||||||
|
map(0x50, MINECRAFT_1_15, true));
|
||||||
clientbound.register(PlayerListItem.class, PlayerListItem::new,
|
clientbound.register(PlayerListItem.class, PlayerListItem::new,
|
||||||
map(0x38, MINECRAFT_1_7_2, false),
|
map(0x38, MINECRAFT_1_7_2, false),
|
||||||
map(0x2D, MINECRAFT_1_9, false),
|
map(0x2D, MINECRAFT_1_9, false),
|
||||||
map(0x2E, MINECRAFT_1_12_1, false),
|
map(0x2E, MINECRAFT_1_12_1, false),
|
||||||
map(0x30, MINECRAFT_1_13, false),
|
map(0x30, MINECRAFT_1_13, false),
|
||||||
map(0x33, MINECRAFT_1_14, false));
|
map(0x33, MINECRAFT_1_14, false),
|
||||||
|
map(0x34, MINECRAFT_1_15, false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LOGIN {
|
LOGIN {
|
||||||
|
@ -18,6 +18,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
|||||||
import io.netty.channel.socket.DatagramPacket;
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -59,6 +60,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
|
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
|
||||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||||
.build();
|
.build();
|
||||||
|
private final SecureRandom random;
|
||||||
|
|
||||||
private volatile @MonotonicNonNull List<QueryResponse.PluginInformation> pluginInformationList
|
private volatile @MonotonicNonNull List<QueryResponse.PluginInformation> pluginInformationList
|
||||||
= null;
|
= null;
|
||||||
@ -67,6 +69,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
|
|
||||||
public GS4QueryHandler(VelocityServer server) {
|
public GS4QueryHandler(VelocityServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
this.random = new SecureRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResponse createInitialResponse() {
|
private QueryResponse createInitialResponse() {
|
||||||
@ -111,7 +114,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case QUERY_TYPE_HANDSHAKE: {
|
case QUERY_TYPE_HANDSHAKE: {
|
||||||
// Generate new challenge token and put it into the sessions cache
|
// Generate new challenge token and put it into the sessions cache
|
||||||
int challengeToken = ThreadLocalRandom.current().nextInt();
|
int challengeToken = random.nextInt();
|
||||||
sessions.put(senderAddress, challengeToken);
|
sessions.put(senderAddress, challengeToken);
|
||||||
|
|
||||||
// Respond with challenge token
|
// Respond with challenge token
|
||||||
|
@ -5,10 +5,10 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
|
|||||||
import com.velocitypowered.natives.util.MoreByteBufUtils;
|
import com.velocitypowered.natives.util.MoreByteBufUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
public class MinecraftCipherDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||||
|
|
||||||
private final VelocityCipher cipher;
|
private final VelocityCipher cipher;
|
||||||
|
|
||||||
@ -18,16 +18,19 @@ public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in);
|
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in).slice();
|
||||||
try {
|
try {
|
||||||
out.add(cipher.process(ctx, compatible));
|
cipher.process(compatible);
|
||||||
} finally {
|
out.add(compatible);
|
||||||
compatible.release();
|
in.skipBytes(in.readableBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
compatible.release(); // compatible will never be used if we throw an exception
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||||
cipher.dispose();
|
cipher.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,11 @@ public class MinecraftCipherEncoder extends MessageToMessageEncoder<ByteBuf> {
|
|||||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||||
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg);
|
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg);
|
||||||
try {
|
try {
|
||||||
out.add(cipher.process(ctx, compatible));
|
cipher.process(compatible);
|
||||||
} finally {
|
out.add(compatible);
|
||||||
compatible.release();
|
} catch (Exception e) {
|
||||||
|
compatible.release(); // compatible will never be used if we throw an exception
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ import java.util.List;
|
|||||||
|
|
||||||
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||||
|
|
||||||
private static final int MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB
|
private static final int SOFT_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB
|
||||||
|
private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 16 * 1024 * 1024; // 16MiB
|
||||||
|
|
||||||
private final int threshold;
|
private final int threshold;
|
||||||
private final VelocityCompressor compressor;
|
private final VelocityCompressor compressor;
|
||||||
@ -25,21 +26,23 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
int expectedSize = ProtocolUtils.readVarInt(in);
|
int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
|
||||||
if (expectedSize == 0) {
|
if (claimedUncompressedSize == 0) {
|
||||||
// Strip the now-useless uncompressed size, this message is already uncompressed.
|
// Strip the now-useless uncompressed size, this message is already uncompressed.
|
||||||
out.add(in.retainedSlice());
|
out.add(in.retainedSlice());
|
||||||
in.skipBytes(in.readableBytes());
|
in.skipBytes(in.readableBytes());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkFrame(expectedSize >= threshold, "Uncompressed size %s is less than threshold %s",
|
checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
|
||||||
expectedSize, threshold);
|
+ " threshold %s", claimedUncompressedSize, threshold);
|
||||||
int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE);
|
int allowedMax = Math.min(claimedUncompressedSize, HARD_MAXIMUM_UNCOMPRESSED_SIZE);
|
||||||
|
int initialCapacity = Math.min(claimedUncompressedSize, SOFT_MAXIMUM_UNCOMPRESSED_SIZE);
|
||||||
|
|
||||||
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
|
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
|
||||||
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
|
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
|
||||||
try {
|
try {
|
||||||
compressor.inflate(compatibleIn, uncompressed, expectedSize);
|
compressor.inflate(compatibleIn, uncompressed, allowedMax);
|
||||||
out.add(uncompressed);
|
out.add(uncompressed);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
uncompressed.release();
|
uncompressed.release();
|
||||||
|
@ -47,20 +47,21 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
|
|||||||
packet.decode(msg, direction, registry.version);
|
packet.decode(msg, direction, registry.version);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new CorruptedFrameException(
|
throw new CorruptedFrameException(
|
||||||
"Error decoding " + packet.getClass() + " Direction " + direction
|
"Error decoding " + packet.getClass() + " " + getExtraConnectionDetail(packetId), e);
|
||||||
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
|
|
||||||
.toHexString(packetId), e);
|
|
||||||
}
|
}
|
||||||
if (msg.isReadable()) {
|
if (msg.isReadable()) {
|
||||||
throw new CorruptedFrameException(
|
throw new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " "
|
||||||
"Did not read full packet for " + packet.getClass() + " Direction " + direction
|
+ getExtraConnectionDetail(packetId));
|
||||||
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
|
|
||||||
.toHexString(packetId));
|
|
||||||
}
|
}
|
||||||
out.add(packet);
|
out.add(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getExtraConnectionDetail(int packetId) {
|
||||||
|
return "Direction " + direction + " Protocol " + registry.version + " State " + state
|
||||||
|
+ " ID " + Integer.toHexString(packetId);
|
||||||
|
}
|
||||||
|
|
||||||
public void setProtocolVersion(ProtocolVersion protocolVersion) {
|
public void setProtocolVersion(ProtocolVersion protocolVersion) {
|
||||||
this.registry = direction.getProtocolRegistry(state, protocolVersion);
|
this.registry = direction.getProtocolRegistry(state, protocolVersion);
|
||||||
}
|
}
|
||||||
|
@ -27,16 +27,13 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
|||||||
// Make sure reader index of length buffer is returned to the beginning
|
// Make sure reader index of length buffer is returned to the beginning
|
||||||
in.readerIndex(origReaderIndex);
|
in.readerIndex(origReaderIndex);
|
||||||
int packetLength = ProtocolUtils.readVarInt(in);
|
int packetLength = ProtocolUtils.readVarInt(in);
|
||||||
if (packetLength == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in.readableBytes() < packetLength) {
|
|
||||||
in.readerIndex(origReaderIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (in.readableBytes() >= packetLength) {
|
||||||
out.add(in.readRetainedSlice(packetLength));
|
out.add(in.readRetainedSlice(packetLength));
|
||||||
|
} else {
|
||||||
|
in.readerIndex(origReaderIndex);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.velocitypowered.proxy.protocol.netty;
|
package com.velocitypowered.proxy.protocol.netty;
|
||||||
|
|
||||||
|
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
||||||
|
import com.velocitypowered.natives.util.Natives;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
@ -11,6 +13,7 @@ import java.util.List;
|
|||||||
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
|
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||||
|
|
||||||
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
||||||
|
private static final boolean IS_JAVA_CIPHER = Natives.cipher.get() == JavaVelocityCipher.FACTORY;
|
||||||
|
|
||||||
private MinecraftVarintLengthEncoder() {
|
private MinecraftVarintLengthEncoder() {
|
||||||
}
|
}
|
||||||
@ -18,7 +21,7 @@ public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBu
|
|||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list)
|
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
ByteBuf lengthBuf = ctx.alloc().buffer(5); // the maximum size of a varint
|
ByteBuf lengthBuf = IS_JAVA_CIPHER ? ctx.alloc().heapBuffer(5) : ctx.alloc().directBuffer(5);
|
||||||
ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes());
|
ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes());
|
||||||
list.add(lengthBuf);
|
list.add(lengthBuf);
|
||||||
list.add(buf.retain());
|
list.add(buf.retain());
|
||||||
|
@ -12,11 +12,13 @@ public class JoinGame implements MinecraftPacket {
|
|||||||
private int entityId;
|
private int entityId;
|
||||||
private short gamemode;
|
private short gamemode;
|
||||||
private int dimension;
|
private int dimension;
|
||||||
|
private long partialHashedSeed; // 1.15+
|
||||||
private short difficulty;
|
private short difficulty;
|
||||||
private short maxPlayers;
|
private short maxPlayers;
|
||||||
private @Nullable String levelType;
|
private @Nullable String levelType;
|
||||||
private int viewDistance; //1.14+
|
private int viewDistance; //1.14+
|
||||||
private boolean reducedDebugInfo;
|
private boolean reducedDebugInfo;
|
||||||
|
private boolean mystery;
|
||||||
|
|
||||||
public int getEntityId() {
|
public int getEntityId() {
|
||||||
return entityId;
|
return entityId;
|
||||||
@ -42,6 +44,10 @@ public class JoinGame implements MinecraftPacket {
|
|||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getPartialHashedSeed() {
|
||||||
|
return partialHashedSeed;
|
||||||
|
}
|
||||||
|
|
||||||
public short getDifficulty() {
|
public short getDifficulty() {
|
||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
@ -91,6 +97,7 @@ public class JoinGame implements MinecraftPacket {
|
|||||||
+ "entityId=" + entityId
|
+ "entityId=" + entityId
|
||||||
+ ", gamemode=" + gamemode
|
+ ", gamemode=" + gamemode
|
||||||
+ ", dimension=" + dimension
|
+ ", dimension=" + dimension
|
||||||
|
+ ", partialHashedSeed=" + partialHashedSeed
|
||||||
+ ", difficulty=" + difficulty
|
+ ", difficulty=" + difficulty
|
||||||
+ ", maxPlayers=" + maxPlayers
|
+ ", maxPlayers=" + maxPlayers
|
||||||
+ ", levelType='" + levelType + '\''
|
+ ", levelType='" + levelType + '\''
|
||||||
@ -111,6 +118,9 @@ public class JoinGame implements MinecraftPacket {
|
|||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||||
this.difficulty = buf.readUnsignedByte();
|
this.difficulty = buf.readUnsignedByte();
|
||||||
}
|
}
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||||
|
this.partialHashedSeed = buf.readLong();
|
||||||
|
}
|
||||||
this.maxPlayers = buf.readUnsignedByte();
|
this.maxPlayers = buf.readUnsignedByte();
|
||||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
|
||||||
@ -119,6 +129,9 @@ public class JoinGame implements MinecraftPacket {
|
|||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
||||||
this.reducedDebugInfo = buf.readBoolean();
|
this.reducedDebugInfo = buf.readBoolean();
|
||||||
}
|
}
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||||
|
this.mystery = buf.readBoolean();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -133,6 +146,9 @@ public class JoinGame implements MinecraftPacket {
|
|||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||||
buf.writeByte(difficulty);
|
buf.writeByte(difficulty);
|
||||||
}
|
}
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||||
|
buf.writeLong(partialHashedSeed);
|
||||||
|
}
|
||||||
buf.writeByte(maxPlayers);
|
buf.writeByte(maxPlayers);
|
||||||
if (levelType == null) {
|
if (levelType == null) {
|
||||||
throw new IllegalStateException("No level type specified.");
|
throw new IllegalStateException("No level type specified.");
|
||||||
@ -144,6 +160,9 @@ public class JoinGame implements MinecraftPacket {
|
|||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
||||||
buf.writeBoolean(reducedDebugInfo);
|
buf.writeBoolean(reducedDebugInfo);
|
||||||
}
|
}
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||||
|
buf.writeBoolean(mystery);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf;
|
|||||||
public class Respawn implements MinecraftPacket {
|
public class Respawn implements MinecraftPacket {
|
||||||
|
|
||||||
private int dimension;
|
private int dimension;
|
||||||
|
private long partialHashedSeed;
|
||||||
private short difficulty;
|
private short difficulty;
|
||||||
private short gamemode;
|
private short gamemode;
|
||||||
private String levelType = "";
|
private String levelType = "";
|
||||||
@ -16,8 +17,10 @@ public class Respawn implements MinecraftPacket {
|
|||||||
public Respawn() {
|
public Respawn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Respawn(int dimension, short difficulty, short gamemode, String levelType) {
|
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
||||||
|
String levelType) {
|
||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
|
this.partialHashedSeed = partialHashedSeed;
|
||||||
this.difficulty = difficulty;
|
this.difficulty = difficulty;
|
||||||
this.gamemode = gamemode;
|
this.gamemode = gamemode;
|
||||||
this.levelType = levelType;
|
this.levelType = levelType;
|
||||||
@ -31,6 +34,14 @@ public class Respawn implements MinecraftPacket {
|
|||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getPartialHashedSeed() {
|
||||||
|
return partialHashedSeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPartialHashedSeed(long partialHashedSeed) {
|
||||||
|
this.partialHashedSeed = partialHashedSeed;
|
||||||
|
}
|
||||||
|
|
||||||
public short getDifficulty() {
|
public short getDifficulty() {
|
||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
@ -59,6 +70,7 @@ public class Respawn implements MinecraftPacket {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "Respawn{"
|
return "Respawn{"
|
||||||
+ "dimension=" + dimension
|
+ "dimension=" + dimension
|
||||||
|
+ ", partialHashedSeed=" + partialHashedSeed
|
||||||
+ ", difficulty=" + difficulty
|
+ ", difficulty=" + difficulty
|
||||||
+ ", gamemode=" + gamemode
|
+ ", gamemode=" + gamemode
|
||||||
+ ", levelType='" + levelType + '\''
|
+ ", levelType='" + levelType + '\''
|
||||||
@ -71,6 +83,9 @@ public class Respawn implements MinecraftPacket {
|
|||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||||
this.difficulty = buf.readUnsignedByte();
|
this.difficulty = buf.readUnsignedByte();
|
||||||
}
|
}
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||||
|
this.partialHashedSeed = buf.readLong();
|
||||||
|
}
|
||||||
this.gamemode = buf.readUnsignedByte();
|
this.gamemode = buf.readUnsignedByte();
|
||||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||||
}
|
}
|
||||||
@ -81,6 +96,9 @@ public class Respawn implements MinecraftPacket {
|
|||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||||
buf.writeByte(difficulty);
|
buf.writeByte(difficulty);
|
||||||
}
|
}
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||||
|
buf.writeLong(partialHashedSeed);
|
||||||
|
}
|
||||||
buf.writeByte(gamemode);
|
buf.writeByte(gamemode);
|
||||||
ProtocolUtils.writeString(buf, levelType);
|
ProtocolUtils.writeString(buf, levelType);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public class VelocityNettyThreadFactory implements ThreadFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Thread newThread(Runnable r) {
|
public Thread newThread(Runnable r) {
|
||||||
String name = String.format(nameFormat, threadNumber.incrementAndGet());
|
String name = String.format(nameFormat, threadNumber.getAndIncrement());
|
||||||
return new FastThreadLocalThread(r, name);
|
return new FastThreadLocalThread(r, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren