geforkt von Mirrors/Velocity
Update to 1.19.1 (#772)
* 1.19.1-rc1 * More signature changes * Further 1.19.1 changes I also started on the checkstyle update, see the developers notes for the rest I haven't gotten around to fixing yet. * Fix checkstyle * Checkstyle imports * Fix logic error * Changes 1.19.1-pre2 * 1.19-pre3 * Progress, some parts still WIP * Overlooked changes * Fix ServerData * Fix ServerLogin send check * Workaround the broken behavior of "No Chat Reports" Note that if we ever choose to enforce chat signatures, then the mod will just break again... not our fault if we do that, you get what you pay for. * more Co-authored-by: Shane Freeder <theboyetronic@gmail.com> Co-authored-by: Andrew Steinborn <git@steinborn.me>
Dieser Commit ist enthalten in:
Ursprung
6be344d919
Commit
1a3fba4250
@ -11,7 +11,6 @@ import com.google.common.base.Preconditions;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +10,6 @@ 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.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +58,8 @@ public enum ProtocolVersion {
|
|||||||
MINECRAFT_1_17_1(756, "1.17.1"),
|
MINECRAFT_1_17_1(756, "1.17.1"),
|
||||||
MINECRAFT_1_18(757, "1.18", "1.18.1"),
|
MINECRAFT_1_18(757, "1.18", "1.18.1"),
|
||||||
MINECRAFT_1_18_2(758, "1.18.2"),
|
MINECRAFT_1_18_2(758, "1.18.2"),
|
||||||
MINECRAFT_1_19(759, "1.19");
|
MINECRAFT_1_19(759, "1.19"),
|
||||||
|
MINECRAFT_1_19_1(760, "1.19.1");
|
||||||
|
|
||||||
private static final int SNAPSHOT_BIT = 30;
|
private static final int SNAPSHOT_BIT = 30;
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
package com.velocitypowered.api.proxy;
|
package com.velocitypowered.api.proxy;
|
||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -7,7 +7,12 @@
|
|||||||
|
|
||||||
package com.velocitypowered.api.proxy.crypto;
|
package com.velocitypowered.api.proxy.crypto;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents session-server cross-signed dated RSA public key.
|
* Represents session-server cross-signed dated RSA public key.
|
||||||
@ -32,4 +37,41 @@ public interface IdentifiedKey extends KeySigned {
|
|||||||
*/
|
*/
|
||||||
boolean verifyDataSignature(byte[] signature, byte[]... toVerify);
|
boolean verifyDataSignature(byte[] signature, byte[]... toVerify);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the signature holders UUID.
|
||||||
|
* Returns null before the {@link com.velocitypowered.api.event.connection.LoginEvent}.
|
||||||
|
*
|
||||||
|
* @return the holder UUID or null if not present
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
UUID getSignatureHolder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the key revision.
|
||||||
|
*
|
||||||
|
* @return the key revision
|
||||||
|
*/
|
||||||
|
Revision getKeyRevision();
|
||||||
|
|
||||||
|
enum Revision {
|
||||||
|
GENERIC_V1(ImmutableSet.of(), ImmutableSet.of(ProtocolVersion.MINECRAFT_1_19)),
|
||||||
|
LINKED_V2(ImmutableSet.of(), ImmutableSet.of(ProtocolVersion.MINECRAFT_1_19_1));
|
||||||
|
|
||||||
|
final Set<Revision> backwardsCompatibleTo;
|
||||||
|
final Set<ProtocolVersion> applicableTo;
|
||||||
|
|
||||||
|
Revision(Set<Revision> backwardsCompatibleTo, Set<ProtocolVersion> applicableTo) {
|
||||||
|
this.backwardsCompatibleTo = backwardsCompatibleTo;
|
||||||
|
this.applicableTo = applicableTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Revision> getBackwardsCompatibleTo() {
|
||||||
|
return backwardsCompatibleTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ProtocolVersion> getApplicableTo() {
|
||||||
|
return applicableTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,8 @@
|
|||||||
package com.velocitypowered.api.proxy.crypto;
|
package com.velocitypowered.api.proxy.crypto;
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
import com.google.common.annotations.Beta;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public interface KeySigned {
|
public interface KeySigned {
|
||||||
@ -56,6 +54,7 @@ public interface KeySigned {
|
|||||||
* signer public key. Note: This will **not** check for
|
* signer public key. Note: This will **not** check for
|
||||||
* expiry. You can check for expiry with {@link KeySigned#hasExpired()}.
|
* expiry. You can check for expiry with {@link KeySigned#hasExpired()}.
|
||||||
* <p>DOES NOT WORK YET FOR MESSAGES AND COMMANDS!</p>
|
* <p>DOES NOT WORK YET FOR MESSAGES AND COMMANDS!</p>
|
||||||
|
* Addendum: Does not work for 1.19.1 until the user has authenticated.
|
||||||
*
|
*
|
||||||
* @return validity of the signature
|
* @return validity of the signature
|
||||||
*/
|
*/
|
||||||
|
@ -71,12 +71,12 @@ public interface TabList {
|
|||||||
/**
|
/**
|
||||||
* Builds a tab list entry.
|
* Builds a tab list entry.
|
||||||
*
|
*
|
||||||
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
|
||||||
* @param profile profile
|
* @param profile profile
|
||||||
* @param displayName display name
|
* @param displayName display name
|
||||||
* @param latency latency
|
* @param latency latency
|
||||||
* @param gameMode game mode
|
* @param gameMode game mode
|
||||||
* @return entry
|
* @return entry
|
||||||
|
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
@ -85,13 +85,13 @@ public interface TabList {
|
|||||||
/**
|
/**
|
||||||
* Builds a tab list entry.
|
* Builds a tab list entry.
|
||||||
*
|
*
|
||||||
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
|
||||||
* @param profile profile
|
* @param profile profile
|
||||||
* @param displayName display name
|
* @param displayName display name
|
||||||
* @param latency latency
|
* @param latency latency
|
||||||
* @param gameMode game mode
|
* @param gameMode game mode
|
||||||
* @param key the player key
|
* @param key the player key
|
||||||
* @return entry
|
* @return entry
|
||||||
|
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
|
@ -160,6 +160,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
* Sets the {@link IdentifiedKey} of the {@link TabListEntry}.
|
* Sets the {@link IdentifiedKey} of the {@link TabListEntry}.
|
||||||
* <p>This is only intended and only works for players currently <b>not</b> connected to this proxy.</p>
|
* <p>This is only intended and only works for players currently <b>not</b> connected to this proxy.</p>
|
||||||
* <p>For any player currently connected to this proxy this will be filled automatically.</p>
|
* <p>For any player currently connected to this proxy this will be filled automatically.</p>
|
||||||
|
* <p>Will ignore mismatching key revisions data.</p>
|
||||||
*
|
*
|
||||||
* @param playerKey key to set
|
* @param playerKey key to set
|
||||||
* @return {@code this}, for chaining
|
* @return {@code this}, for chaining
|
||||||
|
@ -18,7 +18,6 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,6 @@ import java.time.Duration;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.checkerframework.common.value.qual.IntRange;
|
import org.checkerframework.common.value.qual.IntRange;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@ -5,57 +5,86 @@
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Checkstyle configuration that checks the Google coding conventions from Google Java Style
|
Checkstyle configuration that checks the Google coding conventions from Google Java Style
|
||||||
that can be found at https://google.github.io/styleguide/javaguide.html.
|
that can be found at https://google.github.io/styleguide/javaguide.html
|
||||||
|
|
||||||
Checkstyle is very configurable. Be sure to read the documentation at
|
Checkstyle is very configurable. Be sure to read the documentation at
|
||||||
http://checkstyle.sf.net (or in your downloaded distribution).
|
http://checkstyle.org (or in your downloaded distribution).
|
||||||
|
|
||||||
To completely disable a check, just comment it out or delete it from the file.
|
To completely disable a check, just comment it out or delete it from the file.
|
||||||
|
To suppress certain violations please review suppression filters.
|
||||||
|
|
||||||
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
|
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Developer notes: Fix MissingJavadocMethod/Type RequireEmptyLineBeforeBlockTagGroup and CustomImportOrder
|
||||||
|
-->
|
||||||
<module name = "Checker">
|
<module name = "Checker">
|
||||||
<property name="charset" value="UTF-8"/>
|
<property name="charset" value="UTF-8"/>
|
||||||
|
|
||||||
<property name="severity" value="warning"/>
|
<property name="severity" value="warning"/>
|
||||||
|
|
||||||
<property name="fileExtensions" value="java, properties, xml"/>
|
<property name="fileExtensions" value="java, properties, xml"/>
|
||||||
|
<!-- Excludes all 'module-info.java' files -->
|
||||||
|
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||||
|
<module name="BeforeExecutionExclusionFileFilter">
|
||||||
|
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||||
|
</module>
|
||||||
|
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
|
||||||
|
default="checkstyle-suppressions.xml" />
|
||||||
|
<property name="optional" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
<!-- Checks for whitespace -->
|
<!-- Checks for whitespace -->
|
||||||
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
|
<!-- See http://checkstyle.org/config_whitespace.html -->
|
||||||
<module name="FileTabCharacter">
|
<module name="FileTabCharacter">
|
||||||
<property name="eachLine" value="true"/>
|
<property name="eachLine" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="fileExtensions" value="java"/>
|
||||||
|
<property name="max" value="120"/>
|
||||||
|
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
<module name="TreeWalker">
|
<module name="TreeWalker">
|
||||||
<module name="OuterTypeFilename"/>
|
<module name="OuterTypeFilename"/>
|
||||||
<!-- <module name="IllegalTokenText">
|
<module name="IllegalTokenText">
|
||||||
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
||||||
<property name="format"
|
<property name="format"
|
||||||
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
|
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
|
||||||
<property name="message"
|
<property name="message"
|
||||||
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
||||||
</module> -->
|
</module>
|
||||||
<module name="AvoidEscapedUnicodeCharacters">
|
<module name="AvoidEscapedUnicodeCharacters">
|
||||||
<property name="allowEscapesForControlCharacters" value="true"/>
|
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||||
<property name="allowByTailComment" value="true"/>
|
<property name="allowByTailComment" value="true"/>
|
||||||
<property name="allowNonPrintableEscapes" value="true"/>
|
<property name="allowNonPrintableEscapes" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="LineLength">
|
|
||||||
<property name="max" value="120"/>
|
|
||||||
<property name="ignorePattern"
|
|
||||||
value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
|
|
||||||
</module>
|
|
||||||
<module name="AvoidStarImport"/>
|
<module name="AvoidStarImport"/>
|
||||||
<module name="OneTopLevelClass"/>
|
<module name="OneTopLevelClass"/>
|
||||||
<module name="NoLineWrap"/>
|
<module name="NoLineWrap">
|
||||||
|
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
|
||||||
|
</module>
|
||||||
<module name="EmptyBlock">
|
<module name="EmptyBlock">
|
||||||
<property name="option" value="TEXT"/>
|
<property name="option" value="TEXT"/>
|
||||||
<property name="tokens"
|
<property name="tokens"
|
||||||
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
|
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="NeedBraces"/>
|
<module name="NeedBraces">
|
||||||
<module name="LeftCurly"/>
|
<property name="tokens"
|
||||||
|
value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
|
||||||
|
</module>
|
||||||
|
<module name="LeftCurly">
|
||||||
|
<property name="tokens"
|
||||||
|
value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
|
||||||
|
INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT,
|
||||||
|
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
|
||||||
|
OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
<module name="RightCurly">
|
<module name="RightCurly">
|
||||||
<property name="id" value="RightCurlySame"/>
|
<property name="id" value="RightCurlySame"/>
|
||||||
<property name="tokens"
|
<property name="tokens"
|
||||||
@ -67,15 +96,40 @@
|
|||||||
<property name="option" value="alone"/>
|
<property name="option" value="alone"/>
|
||||||
<property name="tokens"
|
<property name="tokens"
|
||||||
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
|
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
|
||||||
INSTANCE_INIT"/>
|
INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF,
|
||||||
|
COMPACT_CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="SuppressionXpathSingleFilter">
|
||||||
|
<!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
|
||||||
|
<property name="id" value="RightCurlyAlone"/>
|
||||||
|
<property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
|
||||||
|
or preceding-sibling::*[last()][self::LCURLY]]"/>
|
||||||
|
</module>
|
||||||
|
<module name="WhitespaceAfter">
|
||||||
|
<property name="tokens"
|
||||||
|
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, LITERAL_RETURN,
|
||||||
|
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, LITERAL_FINALLY, DO_WHILE, ELLIPSIS,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_CATCH, LAMBDA,
|
||||||
|
LITERAL_YIELD, LITERAL_CASE"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="WhitespaceAround">
|
<module name="WhitespaceAround">
|
||||||
<property name="allowEmptyConstructors" value="true"/>
|
<property name="allowEmptyConstructors" value="true"/>
|
||||||
|
<property name="allowEmptyLambdas" value="true"/>
|
||||||
<property name="allowEmptyMethods" value="true"/>
|
<property name="allowEmptyMethods" value="true"/>
|
||||||
<property name="allowEmptyTypes" value="true"/>
|
<property name="allowEmptyTypes" value="true"/>
|
||||||
<property name="allowEmptyLoops" value="true"/>
|
<property name="allowEmptyLoops" value="true"/>
|
||||||
|
<property name="ignoreEnhancedForColon" value="false"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
|
||||||
|
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
|
||||||
|
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
|
||||||
|
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
|
||||||
|
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
|
||||||
|
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
|
||||||
|
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
|
||||||
<message key="ws.notFollowed"
|
<message key="ws.notFollowed"
|
||||||
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
|
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks
|
||||||
|
may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
|
||||||
<message key="ws.notPreceded"
|
<message key="ws.notPreceded"
|
||||||
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
|
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
|
||||||
</module>
|
</module>
|
||||||
@ -87,6 +141,10 @@
|
|||||||
<module name="UpperEll"/>
|
<module name="UpperEll"/>
|
||||||
<module name="ModifierOrder"/>
|
<module name="ModifierOrder"/>
|
||||||
<module name="EmptyLineSeparator">
|
<module name="EmptyLineSeparator">
|
||||||
|
<property name="tokens"
|
||||||
|
value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||||
|
STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF,
|
||||||
|
COMPACT_CTOR_DEF"/>
|
||||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="SeparatorWrap">
|
<module name="SeparatorWrap">
|
||||||
@ -100,13 +158,13 @@
|
|||||||
<property name="option" value="EOL"/>
|
<property name="option" value="EOL"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="SeparatorWrap">
|
<module name="SeparatorWrap">
|
||||||
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
|
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 -->
|
||||||
<property name="id" value="SeparatorWrapEllipsis"/>
|
<property name="id" value="SeparatorWrapEllipsis"/>
|
||||||
<property name="tokens" value="ELLIPSIS"/>
|
<property name="tokens" value="ELLIPSIS"/>
|
||||||
<property name="option" value="EOL"/>
|
<property name="option" value="EOL"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="SeparatorWrap">
|
<module name="SeparatorWrap">
|
||||||
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
|
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 -->
|
||||||
<property name="id" value="SeparatorWrapArrayDeclarator"/>
|
<property name="id" value="SeparatorWrapArrayDeclarator"/>
|
||||||
<property name="tokens" value="ARRAY_DECLARATOR"/>
|
<property name="tokens" value="ARRAY_DECLARATOR"/>
|
||||||
<property name="option" value="EOL"/>
|
<property name="option" value="EOL"/>
|
||||||
@ -122,6 +180,8 @@
|
|||||||
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
||||||
</module>
|
</module>
|
||||||
<module name="TypeName">
|
<module name="TypeName">
|
||||||
|
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||||
|
ANNOTATION_DEF, RECORD_DEF"/>
|
||||||
<message key="name.invalidPattern"
|
<message key="name.invalidPattern"
|
||||||
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
</module>
|
</module>
|
||||||
@ -146,16 +206,30 @@
|
|||||||
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
|
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
</module>
|
</module>
|
||||||
<module name="LocalVariableName">
|
<module name="LocalVariableName">
|
||||||
<property name="tokens" value="VARIABLE_DEF"/>
|
|
||||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||||
<message key="name.invalidPattern"
|
<message key="name.invalidPattern"
|
||||||
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
||||||
</module>
|
</module>
|
||||||
|
<module name="PatternVariableName">
|
||||||
|
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Pattern variable name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
<module name="ClassTypeParameterName">
|
<module name="ClassTypeParameterName">
|
||||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
<message key="name.invalidPattern"
|
<message key="name.invalidPattern"
|
||||||
value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
</module>
|
</module>
|
||||||
|
<module name="RecordComponentName">
|
||||||
|
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Record component name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="RecordTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Record type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
<module name="MethodTypeParameterName">
|
<module name="MethodTypeParameterName">
|
||||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
<message key="name.invalidPattern"
|
<message key="name.invalidPattern"
|
||||||
@ -179,7 +253,7 @@
|
|||||||
</module>
|
</module>
|
||||||
<module name="Indentation">
|
<module name="Indentation">
|
||||||
<property name="basicOffset" value="2"/>
|
<property name="basicOffset" value="2"/>
|
||||||
<property name="braceAdjustment" value="0"/>
|
<property name="braceAdjustment" value="2"/>
|
||||||
<property name="caseIndent" value="2"/>
|
<property name="caseIndent" value="2"/>
|
||||||
<property name="throwsIndent" value="4"/>
|
<property name="throwsIndent" value="4"/>
|
||||||
<property name="lineWrappingIndentation" value="4"/>
|
<property name="lineWrappingIndentation" value="4"/>
|
||||||
@ -188,31 +262,51 @@
|
|||||||
<module name="AbbreviationAsWordInName">
|
<module name="AbbreviationAsWordInName">
|
||||||
<property name="ignoreFinal" value="false"/>
|
<property name="ignoreFinal" value="false"/>
|
||||||
<property name="allowedAbbreviationLength" value="1"/>
|
<property name="allowedAbbreviationLength" value="1"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
|
||||||
|
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
|
||||||
|
RECORD_COMPONENT_DEF"/>
|
||||||
</module>
|
</module>
|
||||||
|
<module name="NoWhitespaceBeforeCaseDefaultColon"/>
|
||||||
<module name="OverloadMethodsDeclarationOrder"/>
|
<module name="OverloadMethodsDeclarationOrder"/>
|
||||||
<module name="VariableDeclarationUsageDistance"/>
|
<module name="VariableDeclarationUsageDistance"/>
|
||||||
<module name="CustomImportOrder">
|
<module name="CustomImportOrder">
|
||||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||||
<property name="separateLineBetweenGroups" value="true"/>
|
<property name="separateLineBetweenGroups" value="true"/>
|
||||||
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
|
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
|
||||||
|
<property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodParamPad">
|
||||||
|
<property name="tokens"
|
||||||
|
value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,
|
||||||
|
SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="MethodParamPad"/>
|
|
||||||
<module name="NoWhitespaceBefore">
|
<module name="NoWhitespaceBefore">
|
||||||
<property name="tokens"
|
<property name="tokens"
|
||||||
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
|
value="COMMA, SEMI, POST_INC, POST_DEC, DOT,
|
||||||
|
LABELED_STAT, METHOD_REF"/>
|
||||||
<property name="allowLineBreaks" value="true"/>
|
<property name="allowLineBreaks" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="ParenPad"/>
|
<module name="ParenPad">
|
||||||
|
<property name="tokens"
|
||||||
|
value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
|
||||||
|
EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
|
||||||
|
METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA,
|
||||||
|
RECORD_DEF"/>
|
||||||
|
</module>
|
||||||
<module name="OperatorWrap">
|
<module name="OperatorWrap">
|
||||||
<property name="option" value="NL"/>
|
<property name="option" value="NL"/>
|
||||||
<property name="tokens"
|
<property name="tokens"
|
||||||
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
|
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
|
||||||
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
|
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF,
|
||||||
|
TYPE_EXTENSION_AND "/>
|
||||||
</module>
|
</module>
|
||||||
<module name="AnnotationLocation">
|
<module name="AnnotationLocation">
|
||||||
<property name="id" value="AnnotationLocationMostCases"/>
|
<property name="id" value="AnnotationLocationMostCases"/>
|
||||||
<property name="tokens"
|
<property name="tokens"
|
||||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF,
|
||||||
|
RECORD_DEF, COMPACT_CTOR_DEF"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="AnnotationLocation">
|
<module name="AnnotationLocation">
|
||||||
<property name="id" value="AnnotationLocationVariables"/>
|
<property name="id" value="AnnotationLocationVariables"/>
|
||||||
@ -220,37 +314,59 @@
|
|||||||
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="NonEmptyAtclauseDescription"/>
|
<module name="NonEmptyAtclauseDescription"/>
|
||||||
|
<module name="InvalidJavadocPosition"/>
|
||||||
<module name="JavadocTagContinuationIndentation"/>
|
<module name="JavadocTagContinuationIndentation"/>
|
||||||
<module name="SummaryJavadoc">
|
<module name="SummaryJavadoc">
|
||||||
<property name="forbiddenSummaryFragments"
|
<property name="forbiddenSummaryFragments"
|
||||||
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="JavadocParagraph"/>
|
<module name="JavadocParagraph"/>
|
||||||
|
<!-- <module name="RequireEmptyLineBeforeBlockTagGroup"/>
|
||||||
|
-->
|
||||||
<module name="AtclauseOrder">
|
<module name="AtclauseOrder">
|
||||||
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||||
<property name="target"
|
<property name="target"
|
||||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="JavadocMethod">
|
<module name="JavadocMethod">
|
||||||
<property name="scope" value="public"/>
|
<property name="accessModifiers" value="public"/>
|
||||||
<property name="allowMissingParamTags" value="true"/>
|
<property name="allowMissingParamTags" value="true"/>
|
||||||
<property name="allowMissingThrowsTags" value="true"/>
|
|
||||||
<property name="allowMissingReturnTag" value="true"/>
|
<property name="allowMissingReturnTag" value="true"/>
|
||||||
|
<property name="allowedAnnotations" value="Override, Test"/>
|
||||||
|
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<!-- <module name="MissingJavadocMethod">
|
||||||
|
<property name="scope" value="public"/>
|
||||||
<property name="minLineCount" value="2"/>
|
<property name="minLineCount" value="2"/>
|
||||||
<property name="allowedAnnotations" value="Override, Test"/>
|
<property name="allowedAnnotations" value="Override, Test"/>
|
||||||
<property name="allowThrowsTagsForSubclasses" value="true"/>
|
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF,
|
||||||
|
COMPACT_CTOR_DEF"/>
|
||||||
</module>
|
</module>
|
||||||
|
<module name="MissingJavadocType">
|
||||||
|
<property name="scope" value="protected"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||||
|
RECORD_DEF, ANNOTATION_DEF"/>
|
||||||
|
<property name="excludeScope" value="nothing"/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
<module name="MethodName">
|
<module name="MethodName">
|
||||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
|
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
|
||||||
<message key="name.invalidPattern"
|
<message key="name.invalidPattern"
|
||||||
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
||||||
</module>
|
</module>
|
||||||
<module name="SingleLineJavadoc">
|
<module name="SingleLineJavadoc"/>
|
||||||
<property name="ignoreInlineTags" value="false"/>
|
|
||||||
</module>
|
|
||||||
<module name="EmptyCatchBlock">
|
<module name="EmptyCatchBlock">
|
||||||
<property name="exceptionVariableName" value="expected"/>
|
<property name="exceptionVariableName" value="expected"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="CommentsIndentation"/>
|
<module name="CommentsIndentation">
|
||||||
|
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
|
||||||
|
</module>
|
||||||
|
<!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
|
||||||
|
<module name="SuppressionXpathFilter">
|
||||||
|
<property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"
|
||||||
|
default="checkstyle-xpath-suppressions.xml" />
|
||||||
|
<property name="optional" value="true"/>
|
||||||
|
</module>
|
||||||
</module>
|
</module>
|
||||||
</module>
|
</module>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
checkstyle {
|
checkstyle {
|
||||||
toolVersion '8.14'
|
toolVersion '10.3.1'
|
||||||
configFile new File(project.rootDir, ['config', 'checkstyle', 'checkstyle.xml'].join(File.separator))
|
configFile new File(project.rootDir, ['config', 'checkstyle', 'checkstyle.xml'].join(File.separator))
|
||||||
|
|
||||||
// The build should immediately fail if we have errors.
|
// The build should immediately fail if we have errors.
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
package com.velocitypowered.proxy;
|
package com.velocitypowered.proxy;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -26,7 +25,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
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.bstats.MetricsBase;
|
import org.bstats.MetricsBase;
|
||||||
|
@ -318,9 +318,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commandManager.setAnnounceProxyCommands(configuration.isAnnounceProxyCommands());
|
commandManager.setAnnounceProxyCommands(configuration.isAnnounceProxyCommands());
|
||||||
if (System.getProperty("auth.forceSecureProfiles") == null) {
|
|
||||||
System.setProperty("auth.forceSecureProfiles", String.valueOf(configuration.isForceKeyAuthentication()));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e);
|
logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e);
|
||||||
LogManager.shutdown();
|
LogManager.shutdown();
|
||||||
|
@ -24,7 +24,6 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
|||||||
import com.velocitypowered.api.command.BrigadierCommand;
|
import com.velocitypowered.api.command.BrigadierCommand;
|
||||||
import com.velocitypowered.api.command.CommandSource;
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
|
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ package com.velocitypowered.proxy.command.builtin;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
@ -36,7 +35,6 @@ import com.velocitypowered.api.proxy.server.RegisteredServer;
|
|||||||
import com.velocitypowered.api.util.ProxyVersion;
|
import com.velocitypowered.api.util.ProxyVersion;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.util.InformationUtils;
|
import com.velocitypowered.proxy.util.InformationUtils;
|
||||||
|
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -47,7 +45,6 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import net.kyori.adventure.identity.Identity;
|
import net.kyori.adventure.identity.Identity;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextComponent;
|
import net.kyori.adventure.text.TextComponent;
|
||||||
|
@ -28,7 +28,6 @@ import com.google.gson.annotations.Expose;
|
|||||||
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
||||||
import com.velocitypowered.api.util.Favicon;
|
import com.velocitypowered.api.util.Favicon;
|
||||||
import com.velocitypowered.proxy.util.AddressUtil;
|
import com.velocitypowered.proxy.util.AddressUtil;
|
||||||
|
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -503,6 +502,14 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
}
|
}
|
||||||
forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
|
forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
if (configVersion == 1.0 || configVersion == 2.0) {
|
||||||
|
config.set("force-key-authentication", config.getOrElse("force-key-authentication", true));
|
||||||
|
config.setComment("force-key-authentication",
|
||||||
|
"Should the proxy enforce the new public key security standard? By default, this is on.");
|
||||||
|
config.set("config-version", configVersion == 2.0 ? "2.5" : "1.5");
|
||||||
|
mustResave = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle any cases where the config needs to be saved again
|
// Handle any cases where the config needs to be saved again
|
||||||
if (mustResave) {
|
if (mustResave) {
|
||||||
config.save();
|
config.save();
|
||||||
|
@ -48,6 +48,7 @@ import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatPreview;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatPreview;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ServerChatPreview;
|
import com.velocitypowered.proxy.protocol.packet.chat.ServerChatPreview;
|
||||||
@ -263,6 +264,10 @@ public interface MinecraftSessionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean handle(PlayerChatCompletion packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
default boolean handle(ServerData serverData) {
|
default boolean handle(ServerData serverData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ public class VelocityConstants {
|
|||||||
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
||||||
public static final int MODERN_FORWARDING_DEFAULT = 1;
|
public static final int MODERN_FORWARDING_DEFAULT = 1;
|
||||||
public static final int MODERN_FORWARDING_WITH_KEY = 2;
|
public static final int MODERN_FORWARDING_WITH_KEY = 2;
|
||||||
|
public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3;
|
||||||
|
public static final int MODERN_FORWARDING_MAX_VERSION = MODERN_FORWARDING_WITH_KEY_V2;
|
||||||
|
|
||||||
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,6 @@ import io.netty.buffer.ByteBufUtil;
|
|||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.handler.timeout.ReadTimeoutException;
|
import io.netty.handler.timeout.ReadTimeoutException;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -281,7 +280,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
this.playerConnection.write(
|
this.playerConnection.write(
|
||||||
new ServerData(pingEvent.getPing().getDescriptionComponent(),
|
new ServerData(pingEvent.getPing().getDescriptionComponent(),
|
||||||
pingEvent.getPing().getFavicon().orElse(null),
|
pingEvent.getPing().getFavicon().orElse(null),
|
||||||
packet.isPreviewsChat())
|
packet.isPreviewsChat(), packet.isSecureChatEnforced())
|
||||||
), playerConnection.eventLoop());
|
), playerConnection.eventLoop());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||||
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
@ -81,17 +82,13 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
|
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
|
||||||
&& packet.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
|
&& packet.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
|
||||||
|
|
||||||
int proposedForwardingVersion = VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
int requestedForwardingVersion = VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||||
// Check version
|
// Check version
|
||||||
if (packet.content().readableBytes() == 1) {
|
if (packet.content().readableBytes() == 1) {
|
||||||
int requested = packet.content().readByte();
|
requestedForwardingVersion = packet.content().readByte();
|
||||||
Preconditions.checkArgument(requested >= VelocityConstants.MODERN_FORWARDING_DEFAULT,
|
|
||||||
"Invalid modern forwarding version");
|
|
||||||
proposedForwardingVersion = Math.min(requested, VelocityConstants.MODERN_FORWARDING_WITH_KEY);
|
|
||||||
}
|
}
|
||||||
ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(),
|
ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(),
|
||||||
serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayer().getGameProfile(),
|
serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayer(), requestedForwardingVersion);
|
||||||
serverConn.getPlayer().getIdentifiedKey(), proposedForwardingVersion);
|
|
||||||
|
|
||||||
LoginPluginResponse response = new LoginPluginResponse(packet.getId(), true, forwardingData);
|
LoginPluginResponse response = new LoginPluginResponse(packet.getId(), true, forwardingData);
|
||||||
mc.write(response);
|
mc.write(response);
|
||||||
@ -176,24 +173,61 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int findForwardingVersion(int requested, ConnectedPlayer player) {
|
||||||
|
// Ensure we are in range
|
||||||
|
requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION);
|
||||||
|
if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) {
|
||||||
|
if (player.getIdentifiedKey() != null) {
|
||||||
|
// No enhanced switch on java 11
|
||||||
|
switch (player.getIdentifiedKey().getKeyRevision()) {
|
||||||
|
case GENERIC_V1:
|
||||||
|
return VelocityConstants.MODERN_FORWARDING_WITH_KEY;
|
||||||
|
// Since V2 is not backwards compatible we have to throw the key if v2 and requested is v1
|
||||||
|
case LINKED_V2:
|
||||||
|
return requested >= VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2
|
||||||
|
? VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2 : VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||||
|
default:
|
||||||
|
return VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
|
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
|
||||||
GameProfile profile, @Nullable IdentifiedKey playerKey,
|
ConnectedPlayer player, int requestedVersion) {
|
||||||
int requestedVersion) {
|
|
||||||
ByteBuf forwarded = Unpooled.buffer(2048);
|
ByteBuf forwarded = Unpooled.buffer(2048);
|
||||||
try {
|
try {
|
||||||
int actualVersion = requestedVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY
|
int actualVersion = findForwardingVersion(requestedVersion, player);
|
||||||
? (playerKey != null ? requestedVersion : VelocityConstants.MODERN_FORWARDING_DEFAULT) : requestedVersion;
|
|
||||||
|
|
||||||
ProtocolUtils.writeVarInt(forwarded, actualVersion);
|
ProtocolUtils.writeVarInt(forwarded, actualVersion);
|
||||||
ProtocolUtils.writeString(forwarded, address);
|
ProtocolUtils.writeString(forwarded, address);
|
||||||
ProtocolUtils.writeUuid(forwarded, profile.getId());
|
ProtocolUtils.writeUuid(forwarded, player.getGameProfile().getId());
|
||||||
ProtocolUtils.writeString(forwarded, profile.getName());
|
ProtocolUtils.writeString(forwarded, player.getGameProfile().getName());
|
||||||
ProtocolUtils.writeProperties(forwarded, profile.getProperties());
|
ProtocolUtils.writeProperties(forwarded, player.getGameProfile().getProperties());
|
||||||
|
|
||||||
// This serves as additional redundancy. The key normally is stored in the
|
// This serves as additional redundancy. The key normally is stored in the
|
||||||
// login start to the server, but some setups require this.
|
// login start to the server, but some setups require this.
|
||||||
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY) {
|
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY) {
|
||||||
ProtocolUtils.writePlayerKey(forwarded, playerKey);
|
IdentifiedKey key = player.getIdentifiedKey();
|
||||||
|
assert key != null;
|
||||||
|
ProtocolUtils.writePlayerKey(forwarded, key);
|
||||||
|
|
||||||
|
// Provide the signer UUID since the UUID may differ from the
|
||||||
|
// assigned UUID. Doing that breaks the signatures anyway but the server
|
||||||
|
// should be able to verify the key independently.
|
||||||
|
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2) {
|
||||||
|
if (key.getSignatureHolder() != null) {
|
||||||
|
forwarded.writeBoolean(true);
|
||||||
|
ProtocolUtils.writeUuid(forwarded, key.getSignatureHolder());
|
||||||
|
} else {
|
||||||
|
// Should only not be provided if the player was connected
|
||||||
|
// as offline-mode and the signer UUID was not backfilled
|
||||||
|
forwarded.writeBoolean(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
|
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
|
||||||
|
@ -27,6 +27,7 @@ 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.event.player.PlayerChooseInitialServerEvent;
|
||||||
import com.velocitypowered.api.permission.PermissionFunction;
|
import com.velocitypowered.api.permission.PermissionFunction;
|
||||||
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
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.api.util.UuidUtils;
|
import com.velocitypowered.api.util.UuidUtils;
|
||||||
@ -35,10 +36,12 @@ import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
|||||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
|
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
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 io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@ -131,6 +134,32 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
|||||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) {
|
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) {
|
||||||
playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername());
|
playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (player.getIdentifiedKey() != null) {
|
||||||
|
IdentifiedKey playerKey = player.getIdentifiedKey();
|
||||||
|
if (playerKey.getSignatureHolder() == null) {
|
||||||
|
if (playerKey instanceof IdentifiedKeyImpl) {
|
||||||
|
IdentifiedKeyImpl unlinkedKey = (IdentifiedKeyImpl) playerKey;
|
||||||
|
// Failsafe
|
||||||
|
if (!unlinkedKey.internalAddHolder(player.getUniqueId())) {
|
||||||
|
if (onlineMode) {
|
||||||
|
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
logger.warn("Key for player " + player.getUsername() + " could not be verified!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("A custom key type has been set for player " + player.getUsername());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!Objects.equals(playerKey.getSignatureHolder(), playerUniqueId)) {
|
||||||
|
logger.warn("UUID for Player " + player.getUsername() + " mismatches! "
|
||||||
|
+ "Chat/Commands signatures will not work correctly for this player!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||||
success.setUsername(player.getUsername());
|
success.setUsername(player.getUsername());
|
||||||
success.setProperties(player.getGameProfileProperties());
|
success.setProperties(player.getGameProfileProperties());
|
||||||
|
@ -32,6 +32,7 @@ import com.velocitypowered.api.event.player.PlayerChatEvent;
|
|||||||
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
||||||
import com.velocitypowered.api.event.player.TabCompleteEvent;
|
import com.velocitypowered.api.event.player.TabCompleteEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
@ -67,7 +68,6 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -170,15 +170,39 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
if (chatResult.isAllowed()) {
|
if (chatResult.isAllowed()) {
|
||||||
Optional<String> eventMsg = pme.getResult().getMessage();
|
Optional<String> eventMsg = pme.getResult().getMessage();
|
||||||
if (eventMsg.isPresent()) {
|
if (eventMsg.isPresent()) {
|
||||||
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
|
String messageNew = eventMsg.get();
|
||||||
&& player.getIdentifiedKey() != null) {
|
if (player.getIdentifiedKey() != null) {
|
||||||
|
if (!messageNew.equals(signedMessage.getMessage())) {
|
||||||
|
if (player.getIdentifiedKey().getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
||||||
|
// Bad, very bad.
|
||||||
|
logger.fatal("A plugin tried to change a signed chat message. "
|
||||||
|
+ "This is no longer possible in 1.19.1 and newer. "
|
||||||
|
+ "Disconnecting player " + player.getUsername());
|
||||||
|
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
||||||
|
+ "Contact your network administrator."));
|
||||||
|
} else {
|
||||||
logger.warn("A plugin changed a signed chat message. The server may not accept it.");
|
logger.warn("A plugin changed a signed chat message. The server may not accept it.");
|
||||||
}
|
|
||||||
smc.write(ChatBuilder.builder(player.getProtocolVersion())
|
smc.write(ChatBuilder.builder(player.getProtocolVersion())
|
||||||
.message(event.getMessage()).toServer());
|
.message(messageNew).toServer());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
smc.write(original);
|
smc.write(original);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
smc.write(ChatBuilder.builder(player.getProtocolVersion())
|
||||||
|
.message(messageNew).toServer());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
smc.write(original);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (player.getIdentifiedKey().getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
||||||
|
logger.fatal("A plugin tried to cancel a signed chat message."
|
||||||
|
+ " This is no longer possible in 1.19.1 and newer. "
|
||||||
|
+ "Disconnecting player " + player.getUsername());
|
||||||
|
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
||||||
|
+ "Contact your network administrator."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, smc.eventLoop())
|
}, smc.eventLoop())
|
||||||
.exceptionally((ex) -> {
|
.exceptionally((ex) -> {
|
||||||
@ -702,7 +726,16 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
private CompletableFuture<Void> processCommandExecuteResult(String originalCommand,
|
private CompletableFuture<Void> processCommandExecuteResult(String originalCommand,
|
||||||
CommandResult result,
|
CommandResult result,
|
||||||
@Nullable SignedChatCommand signedCommand) {
|
@Nullable SignedChatCommand signedCommand) {
|
||||||
if (result == CommandResult.denied()) {
|
IdentifiedKey playerKey = player.getIdentifiedKey();
|
||||||
|
if (result == CommandResult.denied() && playerKey != null) {
|
||||||
|
if (signedCommand != null && playerKey.getKeyRevision()
|
||||||
|
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
||||||
|
logger.fatal("A plugin tried to deny a command with signable component(s). "
|
||||||
|
+ "This is not supported. "
|
||||||
|
+ "Disconnecting player " + player.getUsername());
|
||||||
|
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
||||||
|
+ "Contact your network administrator."));
|
||||||
|
}
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -724,6 +757,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
|
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
|
||||||
write.message(signedCommand);
|
write.message(signedCommand);
|
||||||
} else {
|
} else {
|
||||||
|
if (signedCommand != null && playerKey != null && playerKey.getKeyRevision()
|
||||||
|
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
||||||
|
logger.fatal("A plugin tried to change a command with signed component(s). "
|
||||||
|
+ "This is not supported. "
|
||||||
|
+ "Disconnecting player " + player.getUsername());
|
||||||
|
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
||||||
|
+ "Contact your network administrator."));
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
write.message("/" + commandToRun);
|
write.message("/" + commandToRun);
|
||||||
}
|
}
|
||||||
return CompletableFuture.runAsync(() -> smc.write(write.toServer()), smc.eventLoop());
|
return CompletableFuture.runAsync(() -> smc.write(write.toServer()), smc.eventLoop());
|
||||||
@ -738,6 +780,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
|
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
|
||||||
write.message(signedCommand);
|
write.message(signedCommand);
|
||||||
} else {
|
} else {
|
||||||
|
if (signedCommand != null && playerKey != null && playerKey.getKeyRevision()
|
||||||
|
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
||||||
|
logger.fatal("A plugin tried to change a command with signed component(s). "
|
||||||
|
+ "This is not supported. "
|
||||||
|
+ "Disconnecting player " + player.getUsername());
|
||||||
|
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
||||||
|
+ "Contact your network administrator."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
write.message("/" + commandToRun);
|
write.message("/" + commandToRun);
|
||||||
}
|
}
|
||||||
smc.write(write.toServer());
|
smc.write(write.toServer());
|
||||||
@ -746,6 +797,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleCommandForward() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immediately send any queued messages to the server.
|
* Immediately send any queued messages to the server.
|
||||||
*/
|
*/
|
||||||
|
@ -1068,7 +1068,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentifiedKey getIdentifiedKey() {
|
public @Nullable IdentifiedKey getIdentifiedKey() {
|
||||||
return playerKey;
|
return playerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,14 +25,19 @@ import static com.velocitypowered.proxy.crypto.EncryptionUtils.generateServerId;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
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.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
|
import com.velocitypowered.api.util.UuidUtils;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
|
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||||
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;
|
||||||
@ -45,6 +50,7 @@ import java.security.KeyPair;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@ -54,6 +60,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
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;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||||
|
|
||||||
@ -75,7 +82,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
this.server = Preconditions.checkNotNull(server, "server");
|
this.server = Preconditions.checkNotNull(server, "server");
|
||||||
this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection");
|
this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection");
|
||||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||||
this.forceKeyAuthentication = Boolean.getBoolean("auth.forceSecureProfiles");
|
this.forceKeyAuthentication = System.getProperties().containsKey("auth.forceSecureProfiles")
|
||||||
|
? Boolean.getBoolean("auth.forceSecureProfiles") : server.getConfiguration().isForceKeyAuthentication();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -89,7 +97,16 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!playerKey.isSignatureValid()) {
|
boolean isKeyValid;
|
||||||
|
if (playerKey.getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
|
||||||
|
&& playerKey instanceof IdentifiedKeyImpl) {
|
||||||
|
IdentifiedKeyImpl keyImpl = (IdentifiedKeyImpl) playerKey;
|
||||||
|
isKeyValid = keyImpl.internalAddHolder(packet.getHolderUuid());
|
||||||
|
} else {
|
||||||
|
isKeyValid = playerKey.isSignatureValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isKeyValid) {
|
||||||
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -214,9 +231,19 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
try {
|
try {
|
||||||
Response profileResponse = hasJoinedResponse.get();
|
Response profileResponse = hasJoinedResponse.get();
|
||||||
if (profileResponse.getStatusCode() == 200) {
|
if (profileResponse.getStatusCode() == 200) {
|
||||||
|
final GameProfile profile = GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class);
|
||||||
|
// Not so fast, now we verify the public key for 1.19.1+
|
||||||
|
if (inbound.getIdentifiedKey() != null
|
||||||
|
&& inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
|
||||||
|
&& inbound.getIdentifiedKey() instanceof IdentifiedKeyImpl) {
|
||||||
|
IdentifiedKeyImpl key = (IdentifiedKeyImpl) inbound.getIdentifiedKey();
|
||||||
|
if (!key.internalAddHolder(profile.getId())) {
|
||||||
|
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||||
|
}
|
||||||
|
}
|
||||||
// All went well, initialize the session.
|
// All went well, initialize the session.
|
||||||
mcConnection.setSessionHandler(new AuthSessionHandler(
|
mcConnection.setSessionHandler(new AuthSessionHandler(
|
||||||
server, inbound, GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class), true
|
server, inbound, profile, true
|
||||||
));
|
));
|
||||||
} else if (profileResponse.getStatusCode() == 204) {
|
} else if (profileResponse.getStatusCode() == 204) {
|
||||||
// Apparently an offline-mode user logged onto this online-mode proxy.
|
// Apparently an offline-mode user logged onto this online-mode proxy.
|
||||||
|
@ -20,11 +20,9 @@ package com.velocitypowered.proxy.connection.registry;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import net.kyori.adventure.nbt.BinaryTag;
|
import net.kyori.adventure.nbt.BinaryTag;
|
||||||
import net.kyori.adventure.nbt.BinaryTagTypes;
|
import net.kyori.adventure.nbt.BinaryTagTypes;
|
||||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||||
|
@ -27,7 +27,6 @@ import com.velocitypowered.proxy.VelocityServer;
|
|||||||
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import net.kyori.adventure.audience.MessageType;
|
import net.kyori.adventure.audience.MessageType;
|
||||||
import net.kyori.adventure.identity.Identity;
|
import net.kyori.adventure.identity.Identity;
|
||||||
import net.kyori.adventure.permission.PermissionChecker;
|
import net.kyori.adventure.permission.PermissionChecker;
|
||||||
|
@ -21,7 +21,6 @@ import com.google.common.base.Preconditions;
|
|||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import it.unimi.dsi.fastutil.Pair;
|
import it.unimi.dsi.fastutil.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -18,29 +18,37 @@
|
|||||||
package com.velocitypowered.proxy.crypto;
|
package com.velocitypowered.proxy.crypto;
|
||||||
|
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.UUID;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class IdentifiedKeyImpl implements IdentifiedKey {
|
public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||||
|
|
||||||
|
private final Revision revision;
|
||||||
private final PublicKey publicKey;
|
private final PublicKey publicKey;
|
||||||
private final byte[] signature;
|
private final byte[] signature;
|
||||||
private final Instant expiryTemporal;
|
private final Instant expiryTemporal;
|
||||||
private @MonotonicNonNull Boolean isSignatureValid;
|
private @MonotonicNonNull Boolean isSignatureValid;
|
||||||
|
private @MonotonicNonNull UUID holder;
|
||||||
|
|
||||||
public IdentifiedKeyImpl(byte[] keyBits, long expiry,
|
public IdentifiedKeyImpl(Revision revision, byte[] keyBits, long expiry,
|
||||||
byte[] signature) {
|
byte[] signature) {
|
||||||
this(EncryptionUtils.parseRsaPublicKey(keyBits), Instant.ofEpochMilli(expiry), signature);
|
this(revision, EncryptionUtils.parseRsaPublicKey(keyBits), Instant.ofEpochMilli(expiry), signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Identified key from data.
|
* Creates an Identified key from data.
|
||||||
*/
|
*/
|
||||||
public IdentifiedKeyImpl(PublicKey publicKey, Instant expiryTemporal, byte[] signature) {
|
public IdentifiedKeyImpl(Revision revision, PublicKey publicKey, Instant expiryTemporal, byte[] signature) {
|
||||||
|
this.revision = revision;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.expiryTemporal = expiryTemporal;
|
this.expiryTemporal = expiryTemporal;
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
@ -63,19 +71,68 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getSignature() {
|
public byte[] getSignature() {
|
||||||
return signature;
|
return signature.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable UUID getSignatureHolder() {
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Revision getKeyRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the uuid for this key.
|
||||||
|
* Returns false if incorrect.
|
||||||
|
*/
|
||||||
|
public boolean internalAddHolder(UUID holder) {
|
||||||
|
if (holder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.holder == null) {
|
||||||
|
Boolean result = validateData(holder);
|
||||||
|
if (result == null || !result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
isSignatureValid = true;
|
||||||
|
this.holder = holder;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.holder.equals(holder) && isSignatureValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSignatureValid() {
|
public boolean isSignatureValid() {
|
||||||
if (isSignatureValid == null) {
|
if (isSignatureValid == null) {
|
||||||
|
isSignatureValid = validateData(holder);
|
||||||
|
}
|
||||||
|
return isSignatureValid != null && isSignatureValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean validateData(@Nullable UUID verify) {
|
||||||
|
if (revision == Revision.GENERIC_V1) {
|
||||||
String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey);
|
String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey);
|
||||||
long expires = expiryTemporal.toEpochMilli();
|
long expires = expiryTemporal.toEpochMilli();
|
||||||
byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII);
|
byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII);
|
||||||
isSignatureValid = EncryptionUtils.verifySignature(
|
return EncryptionUtils.verifySignature(
|
||||||
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify);
|
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify);
|
||||||
|
} else {
|
||||||
|
if (verify == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] keyBytes = publicKey.getEncoded();
|
||||||
|
byte[] toVerify = new byte[keyBytes.length + 24]; // length long * 3
|
||||||
|
ByteBuffer fixedDataSet = ByteBuffer.wrap(toVerify).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
fixedDataSet.putLong(verify.getMostSignificantBits());
|
||||||
|
fixedDataSet.putLong(verify.getLeastSignificantBits());
|
||||||
|
fixedDataSet.putLong(expiryTemporal.toEpochMilli());
|
||||||
|
fixedDataSet.put(keyBytes);
|
||||||
|
return EncryptionUtils.verifySignature(EncryptionUtils.SHA1_WITH_RSA,
|
||||||
|
EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify);
|
||||||
}
|
}
|
||||||
return isSignatureValid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,10 +147,12 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "IdentifiedKeyImpl{"
|
return "IdentifiedKeyImpl{"
|
||||||
+ "publicKey=" + publicKey
|
+ "revision=" + revision
|
||||||
|
+ ", publicKey=" + publicKey
|
||||||
+ ", signature=" + Arrays.toString(signature)
|
+ ", signature=" + Arrays.toString(signature)
|
||||||
+ ", expiryTemporal=" + expiryTemporal
|
+ ", expiryTemporal=" + expiryTemporal
|
||||||
+ ", isSignatureValid=" + isSignatureValid
|
+ ", isSignatureValid=" + isSignatureValid
|
||||||
|
+ ", holder=" + holder
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.crypto;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SignaturePair {
|
||||||
|
|
||||||
|
private final UUID signer;
|
||||||
|
private final byte[] signature;
|
||||||
|
|
||||||
|
public SignaturePair(UUID signer, byte[] signature) {
|
||||||
|
this.signer = signer;
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getSigner() {
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SignaturePair{"
|
||||||
|
+ "signer=" + signer
|
||||||
|
+ ", signature=" + Arrays.toString(signature)
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
@ -35,12 +35,17 @@ public class SignedChatCommand implements KeySigned {
|
|||||||
private final boolean isPreviewSigned;
|
private final boolean isPreviewSigned;
|
||||||
|
|
||||||
private final Map<String, byte[]> signatures;
|
private final Map<String, byte[]> signatures;
|
||||||
|
private final SignaturePair[] previousSignatures;
|
||||||
|
private final @Nullable SignaturePair lastSignature;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a signed command from data.
|
* Create a signed command from data.
|
||||||
*/
|
*/
|
||||||
public SignedChatCommand(String command, PublicKey signer, UUID sender,
|
public SignedChatCommand(String command, PublicKey signer, UUID sender,
|
||||||
Instant expiry, Map<String, byte[]> signature, byte[] salt, boolean isPreviewSigned) {
|
Instant expiry, Map<String, byte[]> signature, byte[] salt,
|
||||||
|
boolean isPreviewSigned, SignaturePair[] previousSignatures,
|
||||||
|
@Nullable SignaturePair lastSignature) {
|
||||||
this.command = Preconditions.checkNotNull(command);
|
this.command = Preconditions.checkNotNull(command);
|
||||||
this.signer = Preconditions.checkNotNull(signer);
|
this.signer = Preconditions.checkNotNull(signer);
|
||||||
this.sender = Preconditions.checkNotNull(sender);
|
this.sender = Preconditions.checkNotNull(sender);
|
||||||
@ -48,6 +53,8 @@ public class SignedChatCommand implements KeySigned {
|
|||||||
this.expiry = Preconditions.checkNotNull(expiry);
|
this.expiry = Preconditions.checkNotNull(expiry);
|
||||||
this.salt = Preconditions.checkNotNull(salt);
|
this.salt = Preconditions.checkNotNull(salt);
|
||||||
this.isPreviewSigned = isPreviewSigned;
|
this.isPreviewSigned = isPreviewSigned;
|
||||||
|
this.previousSignatures = previousSignatures;
|
||||||
|
this.lastSignature = lastSignature;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,4 +90,12 @@ public class SignedChatCommand implements KeySigned {
|
|||||||
public boolean isPreviewSigned() {
|
public boolean isPreviewSigned() {
|
||||||
return isPreviewSigned;
|
return isPreviewSigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SignaturePair getLastSignature() {
|
||||||
|
return lastSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignaturePair[] getPreviousSignatures() {
|
||||||
|
return previousSignatures;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,18 +18,13 @@
|
|||||||
package com.velocitypowered.proxy.crypto;
|
package com.velocitypowered.proxy.crypto;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
import com.velocitypowered.api.proxy.crypto.SignedMessage;
|
import com.velocitypowered.api.proxy.crypto.SignedMessage;
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.TemporalAmount;
|
import java.time.temporal.TemporalAmount;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class SignedChatMessage implements SignedMessage {
|
public class SignedChatMessage implements SignedMessage {
|
||||||
@ -46,13 +41,17 @@ public class SignedChatMessage implements SignedMessage {
|
|||||||
private final byte[] salt;
|
private final byte[] salt;
|
||||||
private final UUID sender;
|
private final UUID sender;
|
||||||
//private final boolean isValid;
|
//private final boolean isValid;
|
||||||
|
private final SignaturePair[] previousSignatures;
|
||||||
|
private final @Nullable SignaturePair previousSignature;
|
||||||
private final boolean isPreviewSigned;
|
private final boolean isPreviewSigned;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a signed message from data.
|
* Create a signed message from data.
|
||||||
*/
|
*/
|
||||||
public SignedChatMessage(String message, PublicKey signer, UUID sender,
|
public SignedChatMessage(String message, PublicKey signer, UUID sender,
|
||||||
Instant expiry, byte[] signature, byte[] salt, boolean isPreviewSigned) {
|
Instant expiry, byte[] signature, byte[] salt,
|
||||||
|
boolean isPreviewSigned, @Nullable SignaturePair[] previousSignatures,
|
||||||
|
@Nullable SignaturePair previousSignature) {
|
||||||
this.message = Preconditions.checkNotNull(message);
|
this.message = Preconditions.checkNotNull(message);
|
||||||
this.signer = Preconditions.checkNotNull(signer);
|
this.signer = Preconditions.checkNotNull(signer);
|
||||||
this.sender = Preconditions.checkNotNull(sender);
|
this.sender = Preconditions.checkNotNull(sender);
|
||||||
@ -60,7 +59,8 @@ public class SignedChatMessage implements SignedMessage {
|
|||||||
this.expiry = Preconditions.checkNotNull(expiry);
|
this.expiry = Preconditions.checkNotNull(expiry);
|
||||||
this.salt = Preconditions.checkNotNull(salt);
|
this.salt = Preconditions.checkNotNull(salt);
|
||||||
this.isPreviewSigned = isPreviewSigned;
|
this.isPreviewSigned = isPreviewSigned;
|
||||||
|
this.previousSignatures = previousSignatures;
|
||||||
|
this.previousSignature = previousSignature;
|
||||||
|
|
||||||
//this.isValid = EncryptionUtils.verifySignature(EncryptionUtils.SHA1_WITH_RSA, signer,
|
//this.isValid = EncryptionUtils.verifySignature(EncryptionUtils.SHA1_WITH_RSA, signer,
|
||||||
// signature, salt, EncryptionUtils.longToBigEndianByteArray(
|
// signature, salt, EncryptionUtils.longToBigEndianByteArray(
|
||||||
@ -83,6 +83,14 @@ public class SignedChatMessage implements SignedMessage {
|
|||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SignaturePair[] getPreviousSignatures() {
|
||||||
|
return previousSignatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignaturePair getPreviousSignature() {
|
||||||
|
return previousSignature;
|
||||||
|
}
|
||||||
|
|
||||||
//@Override
|
//@Override
|
||||||
//public boolean isSignatureValid() {
|
//public boolean isSignatureValid() {
|
||||||
// return isValid;
|
// return isValid;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
package com.velocitypowered.proxy.plugin;
|
package com.velocitypowered.proxy.plugin;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.Velocity;
|
import com.velocitypowered.proxy.Velocity;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -34,7 +34,6 @@ import io.netty.buffer.ByteBufUtil;
|
|||||||
import io.netty.handler.codec.CorruptedFrameException;
|
import io.netty.handler.codec.CorruptedFrameException;
|
||||||
import io.netty.handler.codec.DecoderException;
|
import io.netty.handler.codec.DecoderException;
|
||||||
import io.netty.handler.codec.EncoderException;
|
import io.netty.handler.codec.EncoderException;
|
||||||
|
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -42,7 +41,6 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import net.kyori.adventure.nbt.BinaryTagIO;
|
import net.kyori.adventure.nbt.BinaryTagIO;
|
||||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
@ -557,11 +555,13 @@ public enum ProtocolUtils {
|
|||||||
* @param buf the buffer
|
* @param buf the buffer
|
||||||
* @return the key
|
* @return the key
|
||||||
*/
|
*/
|
||||||
public static IdentifiedKey readPlayerKey(ByteBuf buf) {
|
public static IdentifiedKey readPlayerKey(ProtocolVersion version, ByteBuf buf) {
|
||||||
long expiry = buf.readLong();
|
long expiry = buf.readLong();
|
||||||
byte[] key = ProtocolUtils.readByteArray(buf);
|
byte[] key = ProtocolUtils.readByteArray(buf);
|
||||||
byte[] signature = ProtocolUtils.readByteArray(buf, 4096);
|
byte[] signature = ProtocolUtils.readByteArray(buf, 4096);
|
||||||
return new IdentifiedKeyImpl(key, expiry, signature);
|
IdentifiedKey.Revision revision = version.compareTo(ProtocolVersion.MINECRAFT_1_19) == 0
|
||||||
|
? IdentifiedKey.Revision.GENERIC_V1 : IdentifiedKey.Revision.LINKED_V2;
|
||||||
|
return new IdentifiedKeyImpl(revision, key, expiry, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Direction {
|
public enum Direction {
|
||||||
|
@ -30,6 +30,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_17;
|
|||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18_2;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18_2;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
||||||
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_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;
|
||||||
@ -67,6 +68,7 @@ import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
|
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
|
||||||
@ -121,7 +123,8 @@ public enum StateRegistry {
|
|||||||
map(0x01, MINECRAFT_1_12_1, false),
|
map(0x01, MINECRAFT_1_12_1, false),
|
||||||
map(0x05, MINECRAFT_1_13, false),
|
map(0x05, MINECRAFT_1_13, false),
|
||||||
map(0x06, MINECRAFT_1_14, false),
|
map(0x06, MINECRAFT_1_14, false),
|
||||||
map(0x08, MINECRAFT_1_19, false));
|
map(0x08, MINECRAFT_1_19, false),
|
||||||
|
map(0x09, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(LegacyChat.class, LegacyChat::new,
|
serverbound.register(LegacyChat.class, LegacyChat::new,
|
||||||
map(0x01, MINECRAFT_1_7_2, false),
|
map(0x01, MINECRAFT_1_7_2, false),
|
||||||
map(0x02, MINECRAFT_1_9, false),
|
map(0x02, MINECRAFT_1_9, false),
|
||||||
@ -129,16 +132,19 @@ public enum StateRegistry {
|
|||||||
map(0x02, MINECRAFT_1_12_1, false),
|
map(0x02, MINECRAFT_1_12_1, false),
|
||||||
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
|
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
|
||||||
serverbound.register(PlayerCommand.class, PlayerCommand::new,
|
serverbound.register(PlayerCommand.class, PlayerCommand::new,
|
||||||
map(0x03, MINECRAFT_1_19, false));
|
map(0x03, MINECRAFT_1_19, false),
|
||||||
|
map(0x04, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(PlayerChat.class, PlayerChat::new,
|
serverbound.register(PlayerChat.class, PlayerChat::new,
|
||||||
map(0x04, MINECRAFT_1_19, false));
|
map(0x04, MINECRAFT_1_19, false),
|
||||||
|
map(0x05, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(ClientSettings.class, ClientSettings::new,
|
serverbound.register(ClientSettings.class, ClientSettings::new,
|
||||||
map(0x15, MINECRAFT_1_7_2, false),
|
map(0x15, MINECRAFT_1_7_2, false),
|
||||||
map(0x04, MINECRAFT_1_9, false),
|
map(0x04, MINECRAFT_1_9, false),
|
||||||
map(0x05, MINECRAFT_1_12, false),
|
map(0x05, MINECRAFT_1_12, false),
|
||||||
map(0x04, MINECRAFT_1_12_1, false),
|
map(0x04, MINECRAFT_1_12_1, false),
|
||||||
map(0x05, MINECRAFT_1_14, false),
|
map(0x05, MINECRAFT_1_14, false),
|
||||||
map(0x07, MINECRAFT_1_19, false));
|
map(0x07, MINECRAFT_1_19, false),
|
||||||
|
map(0x08, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(PluginMessage.class, PluginMessage::new,
|
serverbound.register(PluginMessage.class, PluginMessage::new,
|
||||||
map(0x17, MINECRAFT_1_7_2, false),
|
map(0x17, MINECRAFT_1_7_2, false),
|
||||||
map(0x09, MINECRAFT_1_9, false),
|
map(0x09, MINECRAFT_1_9, false),
|
||||||
@ -147,7 +153,8 @@ public enum StateRegistry {
|
|||||||
map(0x0A, MINECRAFT_1_13, false),
|
map(0x0A, MINECRAFT_1_13, false),
|
||||||
map(0x0B, MINECRAFT_1_14, false),
|
map(0x0B, MINECRAFT_1_14, false),
|
||||||
map(0x0A, MINECRAFT_1_17, false),
|
map(0x0A, MINECRAFT_1_17, false),
|
||||||
map(0x0C, MINECRAFT_1_19, false));
|
map(0x0C, MINECRAFT_1_19, false),
|
||||||
|
map(0x0D, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(KeepAlive.class, KeepAlive::new,
|
serverbound.register(KeepAlive.class, KeepAlive::new,
|
||||||
map(0x00, MINECRAFT_1_7_2, false),
|
map(0x00, MINECRAFT_1_7_2, false),
|
||||||
map(0x0B, MINECRAFT_1_9, false),
|
map(0x0B, MINECRAFT_1_9, false),
|
||||||
@ -157,7 +164,8 @@ public enum StateRegistry {
|
|||||||
map(0x0F, MINECRAFT_1_14, false),
|
map(0x0F, MINECRAFT_1_14, false),
|
||||||
map(0x10, MINECRAFT_1_16, false),
|
map(0x10, MINECRAFT_1_16, false),
|
||||||
map(0x0F, MINECRAFT_1_17, false),
|
map(0x0F, MINECRAFT_1_17, false),
|
||||||
map(0x11, MINECRAFT_1_19, false));
|
map(0x11, MINECRAFT_1_19, false),
|
||||||
|
map(0x12, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new,
|
serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new,
|
||||||
map(0x19, MINECRAFT_1_8, false),
|
map(0x19, MINECRAFT_1_8, false),
|
||||||
map(0x16, MINECRAFT_1_9, false),
|
map(0x16, MINECRAFT_1_9, false),
|
||||||
@ -166,7 +174,8 @@ public enum StateRegistry {
|
|||||||
map(0x1F, MINECRAFT_1_14, false),
|
map(0x1F, MINECRAFT_1_14, false),
|
||||||
map(0x20, MINECRAFT_1_16, false),
|
map(0x20, MINECRAFT_1_16, false),
|
||||||
map(0x21, MINECRAFT_1_16_2, false),
|
map(0x21, MINECRAFT_1_16_2, false),
|
||||||
map(0x23, MINECRAFT_1_19, false));
|
map(0x23, MINECRAFT_1_19, false),
|
||||||
|
map(0x24, MINECRAFT_1_19_1, 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),
|
||||||
@ -206,7 +215,8 @@ public enum StateRegistry {
|
|||||||
map(0x18, MINECRAFT_1_16, false),
|
map(0x18, MINECRAFT_1_16, false),
|
||||||
map(0x17, MINECRAFT_1_16_2, false),
|
map(0x17, MINECRAFT_1_16_2, false),
|
||||||
map(0x18, MINECRAFT_1_17, false),
|
map(0x18, MINECRAFT_1_17, false),
|
||||||
map(0x15, MINECRAFT_1_19, false));
|
map(0x15, MINECRAFT_1_19, false),
|
||||||
|
map(0x16, MINECRAFT_1_19_1, 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),
|
||||||
@ -216,7 +226,8 @@ public enum StateRegistry {
|
|||||||
map(0x1A, MINECRAFT_1_16, false),
|
map(0x1A, MINECRAFT_1_16, false),
|
||||||
map(0x19, MINECRAFT_1_16_2, false),
|
map(0x19, MINECRAFT_1_16_2, false),
|
||||||
map(0x1A, MINECRAFT_1_17, false),
|
map(0x1A, MINECRAFT_1_17, false),
|
||||||
map(0x17, MINECRAFT_1_19, false));
|
map(0x17, MINECRAFT_1_19, false),
|
||||||
|
map(0x19, MINECRAFT_1_19_1, 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),
|
||||||
@ -226,7 +237,8 @@ public enum StateRegistry {
|
|||||||
map(0x20, MINECRAFT_1_16, false),
|
map(0x20, MINECRAFT_1_16, false),
|
||||||
map(0x1F, MINECRAFT_1_16_2, false),
|
map(0x1F, MINECRAFT_1_16_2, false),
|
||||||
map(0x21, MINECRAFT_1_17, false),
|
map(0x21, MINECRAFT_1_17, false),
|
||||||
map(0x1E, MINECRAFT_1_19, false));
|
map(0x1E, MINECRAFT_1_19, false),
|
||||||
|
map(0x20, MINECRAFT_1_19_1, 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),
|
||||||
@ -236,7 +248,8 @@ public enum StateRegistry {
|
|||||||
map(0x25, MINECRAFT_1_16, false),
|
map(0x25, MINECRAFT_1_16, false),
|
||||||
map(0x24, MINECRAFT_1_16_2, false),
|
map(0x24, MINECRAFT_1_16_2, false),
|
||||||
map(0x26, MINECRAFT_1_17, false),
|
map(0x26, MINECRAFT_1_17, false),
|
||||||
map(0x23, MINECRAFT_1_19, false));
|
map(0x23, MINECRAFT_1_19, false),
|
||||||
|
map(0x25, MINECRAFT_1_19_1, 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),
|
||||||
@ -248,7 +261,8 @@ public enum StateRegistry {
|
|||||||
map(0x3A, MINECRAFT_1_16, true),
|
map(0x3A, MINECRAFT_1_16, true),
|
||||||
map(0x39, MINECRAFT_1_16_2, true),
|
map(0x39, MINECRAFT_1_16_2, true),
|
||||||
map(0x3D, MINECRAFT_1_17, true),
|
map(0x3D, MINECRAFT_1_17, true),
|
||||||
map(0x3B, MINECRAFT_1_19, true));
|
map(0x3B, MINECRAFT_1_19, true),
|
||||||
|
map(0x3E, MINECRAFT_1_19_1, true));
|
||||||
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
|
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
|
||||||
map(0x48, MINECRAFT_1_8, false),
|
map(0x48, MINECRAFT_1_8, false),
|
||||||
map(0x32, MINECRAFT_1_9, false),
|
map(0x32, MINECRAFT_1_9, false),
|
||||||
@ -260,7 +274,8 @@ public enum StateRegistry {
|
|||||||
map(0x39, MINECRAFT_1_16, false),
|
map(0x39, MINECRAFT_1_16, false),
|
||||||
map(0x38, MINECRAFT_1_16_2, false),
|
map(0x38, MINECRAFT_1_16_2, false),
|
||||||
map(0x3C, MINECRAFT_1_17, false),
|
map(0x3C, MINECRAFT_1_17, false),
|
||||||
map(0x3A, MINECRAFT_1_19, false));
|
map(0x3A, MINECRAFT_1_19, false),
|
||||||
|
map(0x3D, MINECRAFT_1_19_1, false));
|
||||||
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),
|
||||||
@ -273,7 +288,8 @@ public enum StateRegistry {
|
|||||||
map(0x53, MINECRAFT_1_16, true),
|
map(0x53, MINECRAFT_1_16, true),
|
||||||
map(0x5E, MINECRAFT_1_17, true),
|
map(0x5E, MINECRAFT_1_17, true),
|
||||||
map(0x5F, MINECRAFT_1_18, true),
|
map(0x5F, MINECRAFT_1_18, true),
|
||||||
map(0x60, MINECRAFT_1_19, true));
|
map(0x60, MINECRAFT_1_19, true),
|
||||||
|
map(0x63, MINECRAFT_1_19_1, true));
|
||||||
clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new,
|
clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::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),
|
||||||
@ -285,16 +301,20 @@ public enum StateRegistry {
|
|||||||
map(0x4F, MINECRAFT_1_16, MINECRAFT_1_16_4, true));
|
map(0x4F, MINECRAFT_1_16, MINECRAFT_1_16_4, true));
|
||||||
clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new,
|
clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new,
|
||||||
map(0x57, MINECRAFT_1_17, true),
|
map(0x57, MINECRAFT_1_17, true),
|
||||||
map(0x58, MINECRAFT_1_18, true));
|
map(0x58, MINECRAFT_1_18, true),
|
||||||
|
map(0x5B, MINECRAFT_1_19_1, true));
|
||||||
clientbound.register(TitleTextPacket.class, TitleTextPacket::new,
|
clientbound.register(TitleTextPacket.class, TitleTextPacket::new,
|
||||||
map(0x59, MINECRAFT_1_17, true),
|
map(0x59, MINECRAFT_1_17, true),
|
||||||
map(0x5A, MINECRAFT_1_18, true));
|
map(0x5A, MINECRAFT_1_18, true),
|
||||||
|
map(0x5D, MINECRAFT_1_19_1, true));
|
||||||
clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new,
|
clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new,
|
||||||
map(0x41, MINECRAFT_1_17, true),
|
map(0x41, MINECRAFT_1_17, true),
|
||||||
map(0x40, MINECRAFT_1_19, true));
|
map(0x40, MINECRAFT_1_19, true),
|
||||||
|
map(0x43, MINECRAFT_1_19_1, true));
|
||||||
clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new,
|
clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new,
|
||||||
map(0x5A, MINECRAFT_1_17, true),
|
map(0x5A, MINECRAFT_1_17, true),
|
||||||
map(0x5B, MINECRAFT_1_18, true));
|
map(0x5B, MINECRAFT_1_18, true),
|
||||||
|
map(0x5E, MINECRAFT_1_19_1, true));
|
||||||
clientbound.register(TitleClearPacket.class, TitleClearPacket::new,
|
clientbound.register(TitleClearPacket.class, TitleClearPacket::new,
|
||||||
map(0x10, MINECRAFT_1_17, true),
|
map(0x10, MINECRAFT_1_17, true),
|
||||||
map(0x0D, MINECRAFT_1_19, true));
|
map(0x0D, MINECRAFT_1_19, true));
|
||||||
@ -308,11 +328,16 @@ public enum StateRegistry {
|
|||||||
map(0x33, MINECRAFT_1_16, false),
|
map(0x33, MINECRAFT_1_16, false),
|
||||||
map(0x32, MINECRAFT_1_16_2, false),
|
map(0x32, MINECRAFT_1_16_2, false),
|
||||||
map(0x36, MINECRAFT_1_17, false),
|
map(0x36, MINECRAFT_1_17, false),
|
||||||
map(0x34, MINECRAFT_1_19, false));
|
map(0x34, MINECRAFT_1_19, false),
|
||||||
|
map(0x37, MINECRAFT_1_19_1, false));
|
||||||
clientbound.register(SystemChat.class, SystemChat::new,
|
clientbound.register(SystemChat.class, SystemChat::new,
|
||||||
map(0x5F, MINECRAFT_1_19, true));
|
map(0x5F, MINECRAFT_1_19, true),
|
||||||
|
map(0x62, MINECRAFT_1_19_1, true));
|
||||||
|
clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new,
|
||||||
|
StateRegistry.map(0x15, MINECRAFT_1_19_1, true));
|
||||||
clientbound.register(ServerData.class, ServerData::new,
|
clientbound.register(ServerData.class, ServerData::new,
|
||||||
map(0x3F, MINECRAFT_1_19, false));
|
map(0x3F, MINECRAFT_1_19, false),
|
||||||
|
map(0x42, MINECRAFT_1_19_1, false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LOGIN {
|
LOGIN {
|
||||||
|
@ -46,7 +46,6 @@ import java.util.Optional;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||||
|
@ -78,7 +78,7 @@ public class PlayerListItem implements MinecraftPacket {
|
|||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
if (buf.readBoolean()) {
|
if (buf.readBoolean()) {
|
||||||
item.setPlayerKey(ProtocolUtils.readPlayerKey(buf));
|
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -31,15 +31,17 @@ public class ServerData implements MinecraftPacket {
|
|||||||
private @Nullable Component description;
|
private @Nullable Component description;
|
||||||
private @Nullable Favicon favicon;
|
private @Nullable Favicon favicon;
|
||||||
private boolean previewsChat;
|
private boolean previewsChat;
|
||||||
|
private boolean secureChatEnforced; // Added in 1.19.1
|
||||||
|
|
||||||
public ServerData() {
|
public ServerData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerData(@Nullable Component description, @Nullable Favicon favicon,
|
public ServerData(@Nullable Component description, @Nullable Favicon favicon,
|
||||||
boolean previewsChat) {
|
boolean previewsChat, boolean secureChatEnforced) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.favicon = favicon;
|
this.favicon = favicon;
|
||||||
this.previewsChat = previewsChat;
|
this.previewsChat = previewsChat;
|
||||||
|
this.secureChatEnforced = secureChatEnforced;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -53,6 +55,9 @@ public class ServerData implements MinecraftPacket {
|
|||||||
this.favicon = new Favicon(ProtocolUtils.readString(buf));
|
this.favicon = new Favicon(ProtocolUtils.readString(buf));
|
||||||
}
|
}
|
||||||
this.previewsChat = buf.readBoolean();
|
this.previewsChat = buf.readBoolean();
|
||||||
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
this.secureChatEnforced = buf.readBoolean();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -73,6 +78,9 @@ public class ServerData implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf.writeBoolean(this.previewsChat);
|
buf.writeBoolean(this.previewsChat);
|
||||||
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
buf.writeBoolean(this.secureChatEnforced);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -91,4 +99,8 @@ public class ServerData implements MinecraftPacket {
|
|||||||
public boolean isPreviewsChat() {
|
public boolean isPreviewsChat() {
|
||||||
return previewsChat;
|
return previewsChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSecureChatEnforced() {
|
||||||
|
return secureChatEnforced;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,15 @@ import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ServerLogin implements MinecraftPacket {
|
public class ServerLogin implements MinecraftPacket {
|
||||||
|
|
||||||
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty username!");
|
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty username!");
|
||||||
|
|
||||||
private @Nullable String username;
|
private @Nullable String username;
|
||||||
private @Nullable IdentifiedKey playerKey; // Introduced in 1.19
|
private @Nullable IdentifiedKey playerKey; // Introduced in 1.19
|
||||||
|
private @Nullable UUID holderUuid; // Used for key revision 2
|
||||||
|
|
||||||
public ServerLogin() {
|
public ServerLogin() {
|
||||||
}
|
}
|
||||||
@ -58,6 +61,10 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
this.playerKey = playerKey;
|
this.playerKey = playerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UUID getHolderUuid() {
|
||||||
|
return holderUuid;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ServerLogin{"
|
return "ServerLogin{"
|
||||||
@ -75,7 +82,13 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
if (buf.readBoolean()) {
|
if (buf.readBoolean()) {
|
||||||
playerKey = ProtocolUtils.readPlayerKey(buf);
|
playerKey = ProtocolUtils.readPlayerKey(version, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
if (buf.readBoolean()) {
|
||||||
|
holderUuid = ProtocolUtils.readUuid(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,6 +107,15 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
} else {
|
} else {
|
||||||
buf.writeBoolean(false);
|
buf.writeBoolean(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
if (playerKey != null && playerKey.getSignatureHolder() != null) {
|
||||||
|
buf.writeBoolean(true);
|
||||||
|
ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder());
|
||||||
|
} else {
|
||||||
|
buf.writeBoolean(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,9 +124,20 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
|
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
|
||||||
// legal on the protocol level.
|
// legal on the protocol level.
|
||||||
int base = 1 + (16 * 4);
|
int base = 1 + (16 * 4);
|
||||||
|
// Adjustments for Key-authentication
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
return -1;
|
// + 1 for the boolean present/ not present
|
||||||
//TODO: ## 19
|
// + 8 for the long expiry
|
||||||
|
// + 2 len for varint key size
|
||||||
|
// + 294 for the key
|
||||||
|
// + 2 len for varint signature size
|
||||||
|
// + 512 for signature
|
||||||
|
base += 1 + 8 + 2 + 294 + 2 + 512;
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
// +1 boolean uuid optional
|
||||||
|
// + 2 * 8 for the long msb/lsb
|
||||||
|
base += 1 + 8 + 8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ package com.velocitypowered.proxy.protocol.packet.brigadier;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
@ -23,7 +23,6 @@ import com.mojang.brigadier.context.CommandContext;
|
|||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import com.mojang.brigadier.suggestion.Suggestions;
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -19,7 +19,6 @@ package com.velocitypowered.proxy.protocol.packet.brigadier;
|
|||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
public class RegistryKeyArgumentSerializer implements ArgumentPropertySerializer<RegistryKeyArgument> {
|
public class RegistryKeyArgumentSerializer implements ArgumentPropertySerializer<RegistryKeyArgument> {
|
||||||
|
@ -133,7 +133,7 @@ public class ChatBuilder {
|
|||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
// hard override chat > system for now
|
// hard override chat > system for now
|
||||||
return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM.getId() : type.getId());
|
return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM : type);
|
||||||
} else {
|
} else {
|
||||||
return new LegacyChat(ProtocolUtils.getJsonChatSerializer(version).serialize(msg), type.getId(), identity);
|
return new LegacyChat(ProtocolUtils.getJsonChatSerializer(version).serialize(msg), type.getId(), identity);
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,11 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
|||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
||||||
|
import com.velocitypowered.proxy.crypto.SignaturePair;
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatMessage;
|
import com.velocitypowered.proxy.crypto.SignedChatMessage;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -38,6 +40,13 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
private @Nullable Instant expiry;
|
private @Nullable Instant expiry;
|
||||||
private @Nullable byte[] signature;
|
private @Nullable byte[] signature;
|
||||||
private @Nullable byte[] salt;
|
private @Nullable byte[] salt;
|
||||||
|
private SignaturePair[] previousMessages = new SignaturePair[0];
|
||||||
|
private @Nullable SignaturePair lastMessage;
|
||||||
|
|
||||||
|
public static final int MAXIMUM_PREVIOUS_MESSAGE_COUNT = 5;
|
||||||
|
|
||||||
|
public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES =
|
||||||
|
new QuietDecoderException("Invalid previous messages");
|
||||||
|
|
||||||
public PlayerChat() {
|
public PlayerChat() {
|
||||||
}
|
}
|
||||||
@ -58,6 +67,8 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
this.salt = message.getSalt();
|
this.salt = message.getSalt();
|
||||||
this.signature = message.getSignature();
|
this.signature = message.getSignature();
|
||||||
this.signedPreview = message.isPreviewSigned();
|
this.signedPreview = message.isPreviewSigned();
|
||||||
|
this.lastMessage = message.getPreviousSignature();
|
||||||
|
this.previousMessages = message.getPreviousSignatures();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant getExpiry() {
|
public Instant getExpiry() {
|
||||||
@ -98,6 +109,23 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
if (signedPreview && unsigned) {
|
if (signedPreview && unsigned) {
|
||||||
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
|
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
int size = ProtocolUtils.readVarInt(buf);
|
||||||
|
if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) {
|
||||||
|
throw INVALID_PREVIOUS_MESSAGES;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignaturePair[] lastSignatures = new SignaturePair[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf));
|
||||||
|
}
|
||||||
|
previousMessages = lastSignatures;
|
||||||
|
|
||||||
|
if (buf.readBoolean()) {
|
||||||
|
lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -109,6 +137,23 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
|
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
|
||||||
|
|
||||||
buf.writeBoolean(signedPreview);
|
buf.writeBoolean(signedPreview);
|
||||||
|
|
||||||
|
|
||||||
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, previousMessages.length);
|
||||||
|
for (SignaturePair previousMessage : previousMessages) {
|
||||||
|
ProtocolUtils.writeUuid(buf, previousMessage.getSigner());
|
||||||
|
ProtocolUtils.writeByteArray(buf, previousMessage.getSignature());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastMessage != null) {
|
||||||
|
buf.writeBoolean(true);
|
||||||
|
ProtocolUtils.writeUuid(buf, lastMessage.getSigner());
|
||||||
|
ProtocolUtils.writeByteArray(buf, lastMessage.getSignature());
|
||||||
|
} else {
|
||||||
|
buf.writeBoolean(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,7 +174,8 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SignedChatMessage(message, signer.getSignedPublicKey(), sender, expiry, signature, salt, signedPreview);
|
return new SignedChatMessage(message, signer.getSignedPublicKey(), sender, expiry, signature,
|
||||||
|
salt, signedPreview, previousMessages, lastMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.protocol.packet.chat;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
public class PlayerChatCompletion implements MinecraftPacket {
|
||||||
|
|
||||||
|
private String[] completions;
|
||||||
|
private Action action;
|
||||||
|
|
||||||
|
|
||||||
|
public String[] getCompletions() {
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompletions(String[] completions) {
|
||||||
|
this.completions = completions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(Action action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
action = Action.values()[ProtocolUtils.readVarInt(buf)];
|
||||||
|
completions = ProtocolUtils.readStringArray(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, action.ordinal());
|
||||||
|
ProtocolUtils.writeStringArray(buf, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
|
return handler.handle(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Action {
|
||||||
|
ADD,
|
||||||
|
REMOVE,
|
||||||
|
ALTER
|
||||||
|
}
|
||||||
|
}
|
@ -17,12 +17,16 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
package com.velocitypowered.proxy.protocol.packet.chat;
|
||||||
|
|
||||||
|
import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.INVALID_PREVIOUS_MESSAGES;
|
||||||
|
import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
||||||
|
import com.velocitypowered.proxy.crypto.SignaturePair;
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatCommand;
|
import com.velocitypowered.proxy.crypto.SignedChatCommand;
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatMessage;
|
import com.velocitypowered.proxy.crypto.SignedChatMessage;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
@ -30,9 +34,11 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
|||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class PlayerCommand implements MinecraftPacket {
|
public class PlayerCommand implements MinecraftPacket {
|
||||||
|
|
||||||
@ -46,6 +52,8 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
private Instant timestamp;
|
private Instant timestamp;
|
||||||
private long salt;
|
private long salt;
|
||||||
private boolean signedPreview; // Good god. Please no.
|
private boolean signedPreview; // Good god. Please no.
|
||||||
|
private SignaturePair[] previousMessages = new SignaturePair[0];
|
||||||
|
private @Nullable SignaturePair lastMessage;
|
||||||
private Map<String, byte[]> arguments = ImmutableMap.of();
|
private Map<String, byte[]> arguments = ImmutableMap.of();
|
||||||
|
|
||||||
public boolean isSignedPreview() {
|
public boolean isSignedPreview() {
|
||||||
@ -96,6 +104,8 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
this.timestamp = signedCommand.getExpiryTemporal();
|
this.timestamp = signedCommand.getExpiryTemporal();
|
||||||
this.salt = Longs.fromByteArray(signedCommand.getSalt());
|
this.salt = Longs.fromByteArray(signedCommand.getSalt());
|
||||||
this.signedPreview = signedCommand.isPreviewSigned();
|
this.signedPreview = signedCommand.isPreviewSigned();
|
||||||
|
this.lastMessage = signedCommand.getLastSignature();
|
||||||
|
this.previousMessages = signedCommand.getPreviousSignatures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -124,6 +134,24 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
if (unsigned && signedPreview) {
|
if (unsigned && signedPreview) {
|
||||||
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
|
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
int size = ProtocolUtils.readVarInt(buf);
|
||||||
|
if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) {
|
||||||
|
throw INVALID_PREVIOUS_MESSAGES;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignaturePair[] lastSignatures = new SignaturePair[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf));
|
||||||
|
}
|
||||||
|
previousMessages = lastSignatures;
|
||||||
|
|
||||||
|
if (buf.readBoolean()) {
|
||||||
|
lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -146,6 +174,22 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
|
|
||||||
buf.writeBoolean(signedPreview);
|
buf.writeBoolean(signedPreview);
|
||||||
|
|
||||||
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, previousMessages.length);
|
||||||
|
for (SignaturePair previousMessage : previousMessages) {
|
||||||
|
ProtocolUtils.writeUuid(buf, previousMessage.getSigner());
|
||||||
|
ProtocolUtils.writeByteArray(buf, previousMessage.getSignature());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastMessage != null) {
|
||||||
|
buf.writeBoolean(true);
|
||||||
|
ProtocolUtils.writeUuid(buf, lastMessage.getSigner());
|
||||||
|
ProtocolUtils.writeByteArray(buf, lastMessage.getSignature());
|
||||||
|
} else {
|
||||||
|
buf.writeBoolean(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,8 +202,12 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
* @throws com.velocitypowered.proxy.util.except.QuietDecoderException when mustSign is {@code true} and the signature
|
* @throws com.velocitypowered.proxy.util.except.QuietDecoderException when mustSign is {@code true} and the signature
|
||||||
* is invalid.
|
* is invalid.
|
||||||
*/
|
*/
|
||||||
public SignedChatCommand signedContainer(IdentifiedKey signer, UUID sender, boolean mustSign) {
|
public SignedChatCommand signedContainer(
|
||||||
if (unsigned) {
|
@Nullable IdentifiedKey signer, UUID sender, boolean mustSign) {
|
||||||
|
// There's a certain mod that is very broken that still signs messages but
|
||||||
|
// doesn't provide the player key. This is broken and wrong, but we need to
|
||||||
|
// work around that.
|
||||||
|
if (unsigned || signer == null) {
|
||||||
if (mustSign) {
|
if (mustSign) {
|
||||||
throw EncryptionUtils.INVALID_SIGNATURE;
|
throw EncryptionUtils.INVALID_SIGNATURE;
|
||||||
}
|
}
|
||||||
@ -167,7 +215,20 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new SignedChatCommand(command, signer.getSignedPublicKey(), sender, timestamp,
|
return new SignedChatCommand(command, signer.getSignedPublicKey(), sender, timestamp,
|
||||||
arguments, Longs.toByteArray(salt), signedPreview);
|
arguments, Longs.toByteArray(salt), signedPreview, previousMessages, lastMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PlayerCommand{"
|
||||||
|
+ "unsigned=" + unsigned
|
||||||
|
+ ", command='" + command + '\''
|
||||||
|
+ ", timestamp=" + timestamp
|
||||||
|
+ ", salt=" + salt
|
||||||
|
+ ", signedPreview=" + signedPreview
|
||||||
|
+ ", previousMessages=" + Arrays.toString(previousMessages)
|
||||||
|
+ ", arguments=" + arguments
|
||||||
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -28,15 +28,15 @@ public class SystemChat implements MinecraftPacket {
|
|||||||
|
|
||||||
public SystemChat() {}
|
public SystemChat() {}
|
||||||
|
|
||||||
public SystemChat(Component component, int type) {
|
public SystemChat(Component component, ChatBuilder.ChatType type) {
|
||||||
this.component = component;
|
this.component = component;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component component;
|
private Component component;
|
||||||
private int type;
|
private ChatBuilder.ChatType type;
|
||||||
|
|
||||||
public int getType() {
|
public ChatBuilder.ChatType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,13 +47,27 @@ public class SystemChat implements MinecraftPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
|
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
|
||||||
type = ProtocolUtils.readVarInt(buf);
|
// System chat is never decoded so this doesn't matter for now
|
||||||
|
type = ChatBuilder.ChatType.values()[ProtocolUtils.readVarInt(buf)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(component));
|
ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(component));
|
||||||
ProtocolUtils.writeVarInt(buf, type);
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
|
switch (type) {
|
||||||
|
case SYSTEM:
|
||||||
|
buf.writeBoolean(false);
|
||||||
|
break;
|
||||||
|
case GAME_INFO:
|
||||||
|
buf.writeBoolean(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid chat type");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ProtocolUtils.writeVarInt(buf, type.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,7 +38,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
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.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package com.velocitypowered.proxy.tablist;
|
package com.velocitypowered.proxy.tablist;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
@ -226,8 +227,19 @@ public class VelocityTabList implements TabList {
|
|||||||
if (entries.containsKey(entry.getProfile().getId())) {
|
if (entries.containsKey(entry.getProfile().getId())) {
|
||||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
||||||
|
|
||||||
|
IdentifiedKey selectedKey = packetItem.getPlayerKey();
|
||||||
Optional<Player> existing = proxyServer.getPlayer(entry.getProfile().getId());
|
Optional<Player> existing = proxyServer.getPlayer(entry.getProfile().getId());
|
||||||
existing.ifPresent(value -> packetItem.setPlayerKey(value.getIdentifiedKey()));
|
if (existing.isPresent()) {
|
||||||
|
selectedKey = existing.get().getIdentifiedKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedKey != null
|
||||||
|
&& selectedKey.getKeyRevision().getApplicableTo().contains(connection.getProtocolVersion())
|
||||||
|
&& Objects.equals(selectedKey.getSignatureHolder(), entry.getProfile().getId())) {
|
||||||
|
packetItem.setPlayerKey(selectedKey);
|
||||||
|
} else {
|
||||||
|
packetItem.setPlayerKey(null);
|
||||||
|
}
|
||||||
|
|
||||||
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
|
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ import com.velocitypowered.api.proxy.ProxyServer;
|
|||||||
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.api.util.ProxyVersion;
|
import com.velocitypowered.api.util.ProxyVersion;
|
||||||
|
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Config version. Do not change this
|
# Config version. Do not change this
|
||||||
config-version = "2.0"
|
config-version = "2.5"
|
||||||
|
|
||||||
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.
|
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.
|
||||||
bind = "0.0.0.0:25577"
|
bind = "0.0.0.0:25577"
|
||||||
|
@ -31,7 +31,6 @@ import com.velocitypowered.proxy.testutil.FakePluginManager;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DynamicNode;
|
import org.junit.jupiter.api.DynamicNode;
|
||||||
import org.junit.jupiter.api.DynamicTest;
|
import org.junit.jupiter.api.DynamicTest;
|
||||||
|
@ -38,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||||
import com.velocitypowered.proxy.protocol.packet.StatusPing;
|
import com.velocitypowered.proxy.protocol.packet.StatusPing;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class PacketRegistryTest {
|
class PacketRegistryTest {
|
||||||
|
@ -22,13 +22,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||||
import com.velocitypowered.api.scheduler.TaskStatus;
|
import com.velocitypowered.api.scheduler.TaskStatus;
|
||||||
import com.velocitypowered.proxy.testutil.FakePluginManager;
|
import com.velocitypowered.proxy.testutil.FakePluginManager;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class VelocitySchedulerTest {
|
class VelocitySchedulerTest {
|
||||||
|
@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class EncryptionUtilsTest {
|
class EncryptionUtilsTest {
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren