Compare commits

..
51 Commits
Author SHA1 Message Date
Jones a7b1bfd958 Release 2.1.12 (#402) 2024-10-26 16:01:30 +02:00
Jones d97f124201 Fix timestamp check false positive 2024-10-26 15:30:34 +02:00
Jones 5e9d8b4b29 Optimize packet size checking 2024-10-26 15:07:54 +02:00
Jones c3eabd1265 Fix component serialization on <1.16 (https://github.com/jonesdevelopment/sonar/issues/401) 2024-10-24 14:19:21 +02:00
Jones 9d2752dc32 Release 2.1.11 (#400) 2024-10-23 15:27:19 +02:00
Jones 5fe9c18268 Move "FallbackTimeoutHandler" to "netty" 2024-10-23 02:33:06 +02:00
Jones f93dae9532 Bump Gradle from 8.10.1 to 8.10.2 2024-10-22 21:08:45 +02:00
Jones ab36823ca2 Microoptimize cached tab suggestions 2024-10-22 21:04:56 +02:00
Jones 5c94f06510 Remove useless variables from some events 2024-10-22 20:55:01 +02:00
Jones d069b9aadb Fixed CAPTCHA Y position breaking lighting 2024-10-22 20:44:37 +02:00
Jones 3337009fe4 Fix filled_map item type ID 2024-10-22 20:44:14 +02:00
Jones b010e18552 Remove debug message from encoder 2024-10-22 20:37:47 +02:00
Jones 82b4b3bac7 Optimize EntityType and ItemType 2024-10-22 20:26:59 +02:00
Jones b143ea548a Refactor some protocol-related classes 2024-10-22 20:24:39 +02:00
Jones 689e3ea36b Fix UpdateTime packet on 1.21.2 2024-10-22 20:16:25 +02:00
Jones c9c1a2abb7 Microoptimize "/sonar statistics" 2024-10-22 19:46:44 +02:00
Jones a8aa284a5e Simplify CaffeineCacheRatelimiter initialization 2024-10-22 19:07:22 +02:00
448f8d9af3 1.21.2 Support (#370)
* feat: register 1.21.2 protocol ID

* Fix LoginSuccessPacket for 1.21.2

* Backport changes from https://github.com/jonesdevelopment/sonar/pull/370/files#diff-4e3d725f29d513f2f57f369660b93a7ee1d6414004158562736e2e2976fdf121

* Use Velocity's `writeBinaryTag` method

* Rename some variables for clarity

* Use Velocity's version comparison methods

* Add missing signature checks for 1.19-1.19.2

* Simplify uuid reading in LoginStartPacket

* Add missing chat timestamp checks

* Recode JoinGame packet (and dimensions)

* Add particleStatus to ClientInformationPacket

* Fix teleport incompatibilities on 1.16-1.16.5

* Fix invalid Y position handling

* Remove clientbound array length check

* Improve readability of ChunkDataPacket

* Set default write-timeout to 15000

* Set default rejoin-delay to 5000

* [wip] FallbackVehicleHandler 1.21.2 Support

* Recode the entire vehicle handler

* Fix lag compensation in the vehicle check

* Fix previous merge conflicts (origin/main)

* Inline static strings for better readability

* Remove debugging in vehicle handler

* Set default write-timeout to 10000

* Reduce the number of static imports

* Strictly exempt Geyser from PM/CS checks

* Micro-optimize Geyser check

* Remove unnecessary variable assignment

* Fix some false positives in the vehicle check

* 1.21.2-pre4 Support (#397)

* packet id mapping. tags. pre-3 protocol version. empty codec.

* Fix build. Added `codec_1_21_2.nbt` generated by `NBTGenerator`

* Fix `SetPlayerPositionRotationPacket` encode for 1.21.2-pre3

* Fix wrong `encodeOnly` in client bound KeepAlive registry

* Small codestyle improvements

* Fix `ChunkDataPacket` encode for 1.21.2-pre3

* Fix missing packet registry.

* Fix entity type id for 1.21.2-pre3

* Fix wrong map component id for 1.21.2-pre3

* Add 1.21.2-pre3 block mapping.

* Remove update tags packet. (Only affect crafting recipes)

* Remove tag registry.

* Fix and improve vehicle teleport logic for 1.21.2-pre3

* Set `exceptTeleport` to false after received `TeleportConfirm`

* Fix typo for `expectTeleport`

* Minor codestyle changes

* Fix unexpected behavior in vehicle checks.

* Remove debug log

* Simply fix gravity checks for 1.21.2-pre3

* Change protocol version to pre4

* Fix wrong `encodeOnly` state in packet registry

* Fix some previously introduced merging issues

* Update to 1.21.2-pre5

* Fix possible bug in protocol handler

* Fix wrong `encodeOnly` state

---------

Co-authored-by: Jones <admin@jonesdev.xyz>

* Fix KeepAlive check not working on 1.20.2+

* Don't cap KeepAlive ID in pre-join

* Small codestyle improvements

* Fix a false positive in the vehicle check

* Remove tailing whitespace

* Minecraft 1.21.2-rc1 support

* Minecraft 1.21.2 support

---------

Co-authored-by: Akkariin <fallencrystal@outlook.com>
2024-10-22 18:58:23 +02:00
dependabot[bot] 3d8f61fce3 Bump org.jetbrains:annotations from 26.0.0 to 26.0.1 (#399)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 26.0.0 to 26.0.1.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/26.0.0...26.0.1)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 01:34:29 +00:00
Jones cf27a9fe84 Exempt Bedrock players from the vehicle check 2024-10-15 20:10:13 +02:00
Jones bec6251b2a Exempt Bedrock players from the view distance check 2024-10-15 20:10:00 +02:00
Jones 6497b89f10 Use hashes for command tab completion 2024-10-15 19:29:20 +02:00
Jones 525d67808e Don't print permission message if empty 2024-10-15 19:11:54 +02:00
Jones 8864c0749a Optimize networking-related hot code 2024-10-15 12:35:23 +02:00
Jones 1ec4704f8e Fix configuration state race condition 2024-10-14 20:42:39 +02:00
Jones 8a4937e3af Fix error during configuration phase 2024-10-14 20:31:29 +02:00
dependabot[bot] f90446d4e3 Bump org.jetbrains:annotations from 25.0.0 to 26.0.0 (#396)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 25.0.0 to 26.0.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/25.0.0...26.0.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 01:14:01 +00:00
Jones 66141815cb Make verification world time configurable 2024-10-11 16:34:29 +02:00
dependabot[bot] 3fe7ccd65c Bump com.gradleup.shadow from 8.3.2 to 8.3.3 (#394)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.2 to 8.3.3.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.2...8.3.3)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 14:14:12 +02:00
dependabot[bot] f6203416d1 Bump io.netty:netty-all from 4.1.113.Final to 4.1.114.Final (#393)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.113.Final to 4.1.114.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.113.Final...netty-4.1.114.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 14:14:01 +02:00
Jones 3c73ecf60f Release 2.1.10 (#392) 2024-10-06 14:15:51 +02:00
Jones c116d7b556 Reduce the usage of static imports 2024-10-05 11:01:47 +02:00
Jones 057cda6c7a Remove VerifiedPlayerController#estimatedSize 2024-10-05 10:51:26 +02:00
Jones 3a97a1f949 Add easier API method for verifying players 2024-10-05 10:50:30 +02:00
Jones 69b20769de Add more stability checks to the database 2024-10-05 10:47:06 +02:00
Jones 9b87fde7c6 Implement missing view distance check 2024-10-05 09:38:13 +02:00
Jones b939767ab9 Fix translation error in Spanish translations 2024-10-01 17:04:13 +02:00
Jones d5a8cd5346 Tell user how to disable the welcome message 2024-10-01 17:03:58 +02:00
Jones aa4e4662bb Fix possible position issue with vehicles 2024-10-01 17:01:02 +02:00
dependabot[bot] 29187d4248 Bump org.jetbrains:annotations from 24.1.0 to 25.0.0 (#390)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 24.1.0 to 25.0.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/24.1.0...25.0.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 14:37:16 +02:00
Jones 0c8106eaf6 Release 2.1.9 (#389) 2024-09-29 13:32:05 +02:00
Jones 92ac5fbeb2 Fix block heights varying on different versions 2024-09-29 13:26:09 +02:00
Jones b800a2d154 Use one entity ID for all vehicles 2024-09-28 12:21:44 +02:00
Jones 1957463b81 Fix the Discord link in Polish translations 2024-09-28 11:59:09 +02:00
Jones 25114eabe5 Add verification welcome message to config 2024-09-28 11:58:54 +02:00
Jones a45b526ffb Clean up and improve the API 2024-09-25 17:52:35 +02:00
Jones 7b70d32e84 Don't store the pipeline separately in user 2024-09-25 17:48:51 +02:00
Jones e84c1a54db Small micro-optimizations to networking 2024-09-25 17:42:51 +02:00
Jones fd869ac199 Fix potential NullPointerException 2024-09-25 17:23:00 +02:00
dependabot[bot] abf1479a84 Bump com.gradleup.shadow from 8.3.1 to 8.3.2 (#387)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.1...8.3.2)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:03:49 +02:00
dependabot[bot] c00392e062 Bump bstats from 3.0.3 to 3.1.0 (#386)
Bumps `bstats` from 3.0.3 to 3.1.0.

Updates `org.bstats:bstats-bungeecord` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

Updates `org.bstats:bstats-bukkit` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

Updates `org.bstats:bstats-velocity` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bungeecord
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-velocity
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:03:32 +02:00
123 changed files with 1861 additions and 1827 deletions

No files matched your search

@@ -97,6 +97,6 @@ public interface Sonar {
@NotNull
static Sonar get() {
return SonarSupplier.get();
return SonarSupplier.sonar;
}
}
@@ -19,20 +19,15 @@ package xyz.jonesdev.sonar.api;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.exception.AlreadyInitializedException;
@UtilityClass
public class SonarSupplier {
private Sonar _sonar;
Sonar sonar;
public void set(final @NotNull Sonar instance) {
if (_sonar != null) {
throw new AlreadyInitializedException("Sonar is already initialized");
public void set(final @NotNull Sonar _sonar) {
if (sonar != null) {
throw new IllegalStateException("Sonar is already initialized");
}
_sonar = instance;
}
public @NotNull Sonar get() {
return _sonar;
sonar = _sonar;
}
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2024 Sonar 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 xyz.jonesdev.sonar.api.command;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
@Getter
@RequiredArgsConstructor
@ToString(of = "rawArguments")
public final class CommandInvocation {
private final InvocationSource source;
private final String[] rawArguments;
}
@@ -30,17 +30,15 @@ import xyz.jonesdev.sonar.api.update.UpdateChecker;
import java.util.*;
import static java.util.Collections.emptyList;
public interface SonarCommand {
List<String> TAB_SUGGESTIONS = new ArrayList<>();
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
Map<Integer, List<String>> TAB_SUGGESTIONS = new LinkedHashMap<>();
default void handle(final @NotNull InvocationSource source, final String @NotNull [] args) {
// Check if the player actually has the permission to run the command
if (source.isPlayer() && !source.getPermissionFunction().test("sonar.command")) {
source.sendMessage(Sonar.get().getConfig().getNoPermission());
if (Sonar.get().getConfig().getNoPermission() != null) {
source.sendMessage(Sonar.get().getConfig().getNoPermission());
}
return;
}
@@ -103,29 +101,28 @@ public interface SonarCommand {
}
static void prepareCachedTabSuggestions() {
// Don't re-cache tab suggestions
if (!TAB_SUGGESTIONS.isEmpty()) return;
// Cache tab suggestions
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
TAB_SUGGESTIONS.add(subcommand.getInfo().name());
if (subcommand.getInfo().aliases().length > 0) {
TAB_SUGGESTIONS.addAll(Arrays.asList(subcommand.getInfo().aliases()));
}
final List<String> arguments = Arrays.asList(subcommand.getInfo().arguments());
ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), arguments);
for (final String alias : subcommand.getInfo().aliases()) {
ARG_TAB_SUGGESTIONS.put(alias, arguments);
if (TAB_SUGGESTIONS.isEmpty()) {
final List<String> subcommandsAndAliases = new ArrayList<>();
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
subcommandsAndAliases.add(subcommand.getInfo().name());
final List<String> arguments = Arrays.asList(subcommand.getInfo().arguments());
TAB_SUGGESTIONS.put(subcommand.getInfo().name().hashCode(), arguments);
for (final String alias : subcommand.getInfo().aliases()) {
subcommandsAndAliases.add(alias);
TAB_SUGGESTIONS.put(alias.hashCode(), arguments);
}
}
TAB_SUGGESTIONS.put(-1, subcommandsAndAliases);
}
}
default List<String> getCachedTabSuggestions(final String @NotNull [] arguments) {
if (arguments.length <= 1) {
return TAB_SUGGESTIONS;
} else if (arguments.length == 2) {
final String subcommandName = arguments[0].toLowerCase();
return ARG_TAB_SUGGESTIONS.getOrDefault(subcommandName, emptyList());
if (arguments.length <= 2) {
final int subcommandHash = arguments.length <= 1 ? -1 : arguments[0].toLowerCase().hashCode();
return TAB_SUGGESTIONS.getOrDefault(subcommandHash, Collections.emptyList());
}
return emptyList();
return Collections.emptyList();
}
}
@@ -23,7 +23,6 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import java.util.Objects;
@@ -91,7 +90,7 @@ public abstract class Subcommand {
}
// Execute the sub command from the invocation source with the given arguments
execute(new CommandInvocation(invocationSource, arguments));
execute(invocationSource, arguments);
}
public final @NotNull String getDescription() {
@@ -99,5 +98,5 @@ public abstract class Subcommand {
return Objects.requireNonNull(Sonar.get().getConfig().getMessagesConfig().getString(path));
}
protected abstract void execute(final @NotNull CommandInvocation invocation);
protected abstract void execute(final @NotNull InvocationSource source, final String @NotNull [] args);
}
@@ -195,6 +195,7 @@ public final class SonarConfiguration {
verification.brand.validRegex = Pattern.compile(generalConfig.getString("verification.checks.client-brand.valid-regex"));
verification.brand.maxLength = generalConfig.getInt("verification.checks.client-brand.max-length");
verification.timeOfDay = clamp(generalConfig.getInt("verification.time-of-day"), 0, 24000);
verification.gamemode = Verification.Gamemode.valueOf(generalConfig.getString("verification.gamemode"));
verification.validNameRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-name-regex"));
verification.validLocaleRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-locale-regex"));
@@ -242,8 +243,14 @@ public final class SonarConfiguration {
// Messages
prefix = MiniMessage.miniMessage().deserialize(messagesConfig.getString("prefix"));
noPermission = MiniMessage.miniMessage().deserialize(messagesConfig.getString("commands.no-permission"),
Placeholder.component("prefix", prefix));
final String noPermissionMessage = messagesConfig.getString("commands.no-permission");
if (noPermissionMessage.isEmpty()) {
noPermission = null;
} else {
noPermission = MiniMessage.miniMessage().deserialize(noPermissionMessage,
Placeholder.component("prefix", prefix));
}
SonarCommand.prepareCachedTabSuggestions();
@@ -408,6 +415,7 @@ public final class SonarConfiguration {
}
}
private int timeOfDay;
private boolean checkGeyser;
private boolean logConnections;
private boolean logDuringAttack;
@@ -31,6 +31,7 @@ import org.jetbrains.annotations.Range;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.database.model.VerifiedPlayer;
import xyz.jonesdev.sonar.api.fingerprint.FingerprintingUtil;
import java.io.File;
import java.sql.SQLException;
@@ -45,8 +46,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public final class VerifiedPlayerController {
private static final ExecutorService DB_UPDATE_SERVICE = Executors.newSingleThreadExecutor();
@Getter
private final Set<String> cache = Collections.synchronizedSet(new HashSet<>());
private @Nullable ConnectionSource connectionSource;
@@ -54,6 +53,7 @@ public final class VerifiedPlayerController {
private QueryBuilder<VerifiedPlayer, Integer> queryBuilder;
@Getter
private final @NotNull SonarConfiguration.Database.Type cachedDatabaseType;
private final ExecutorService updateService = Executors.newSingleThreadExecutor();
public VerifiedPlayerController(final @NotNull LibraryManager libraryManager) {
final SonarConfiguration.Database database = Sonar.get().getConfig().getDatabase();
@@ -95,7 +95,6 @@ public final class VerifiedPlayerController {
Sonar.get().getConfig().getGeneralConfig().getString("database.password"),
cachedDatabaseType.getDatabaseType());
// Create database table
try {
TableUtils.createTableIfNotExists(connectionSource, VerifiedPlayer.class);
} catch (SQLException ignored) {
@@ -111,14 +110,16 @@ public final class VerifiedPlayerController {
// Make sure to run the clean task and the caching task in the same thread
// https://github.com/jonesdevelopment/sonar/issues/150
DB_UPDATE_SERVICE.execute(() -> {
try {
// Make sure to clear all outdated entries first
clearOld(database.getMaximumAge());
// Add all entries from the database to the cache
dao.queryForAll().forEach(verifiedPlayer -> cache.add(verifiedPlayer.getFingerprint()));
} catch (SQLException exception) {
exception.printStackTrace(System.err);
updateService.execute(() -> {
if (connectionSource != null) {
try {
// Make sure to clear all outdated entries first
clearOld(database.getMaximumAge());
// Add all entries from the database to the cache
dao.queryForAll().forEach(verifiedPlayer -> cache.add(verifiedPlayer.getFingerprint()));
} catch (SQLException exception) {
exception.printStackTrace(System.err);
}
}
});
} catch (SQLException exception) {
@@ -137,16 +138,17 @@ public final class VerifiedPlayerController {
} catch (Exception exception) {
exception.printStackTrace(System.err);
}
// Make sure not to reuse a closed connection
connectionSource = null;
updateService.shutdown();
}
}
/**
* Clear all old entries using the given timestamp.
* Clear all old entries using the given timestamp
*/
private void clearOld(final @Range(from = 1, to = 365) int maximumAge) throws SQLException {
final long timestamp = Instant.now()
.minus(maximumAge, ChronoUnit.DAYS)
.getEpochSecond() * 1000L; // convert to ms
final long timestamp = Instant.now().minus(maximumAge, ChronoUnit.DAYS).getEpochSecond() * 1000L;
final List<VerifiedPlayer> oldEntries = queryBuilder.where()
.lt("timestamp", new Timestamp(timestamp))
@@ -175,34 +177,36 @@ public final class VerifiedPlayerController {
return;
}
DB_UPDATE_SERVICE.execute(() -> {
// We cannot throw a NullPointerException within the executor service
// because we want to handle the error instead of simply throwing an exception
if (connectionSource == null) {
return;
}
updateService.execute(() -> {
if (connectionSource != null) {
try {
final List<VerifiedPlayer> verifiedPlayer = queryBuilder.where()
.eq("fingerprint", fingerprint)
.query();
try {
final List<VerifiedPlayer> verifiedPlayer = queryBuilder.where()
.eq("fingerprint", fingerprint)
.query();
if (verifiedPlayer != null) {
for (final VerifiedPlayer player : verifiedPlayer) {
dao.delete(player);
if (verifiedPlayer != null) {
for (final VerifiedPlayer player : verifiedPlayer) {
dao.delete(player);
}
}
} catch (SQLException exception) {
exception.printStackTrace(System.err);
}
} catch (SQLException exception) {
exception.printStackTrace(System.err);
}
});
}
/**
* First, cache the player locally and then,
* secondly, asynchronously add the player to the database.
*
* @param player VerifiedPlayer model
* Creates a new VerifiedPlayer model from the given username, host address, and timestamp
*/
public void add(final @NotNull String username, final @NotNull String hostAddress, final long timestamp) {
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
// Add a new VerifiedPlayer object from the given parameters to the database
add(new VerifiedPlayer(fingerprint, timestamp));
}
/**
* Caches the player locally and then adds the player to the database
*/
public void add(final @NotNull VerifiedPlayer player) {
cache.add(player.getFingerprint());
@@ -212,39 +216,27 @@ public final class VerifiedPlayerController {
return;
}
DB_UPDATE_SERVICE.execute(() -> {
// We cannot throw a NullPointerException within the executor service
// because we want to handle the error instead of simply throwing an exception
if (connectionSource == null) {
return;
}
try {
dao.createIfNotExists(player);
} catch (SQLException exception) {
exception.printStackTrace(System.err);
updateService.execute(() -> {
if (connectionSource != null) {
try {
dao.createIfNotExists(player);
} catch (SQLException exception) {
exception.printStackTrace(System.err);
}
}
});
}
/**
* Returns the number of verified IP addresses
*
* @return Estimated size of the local cache
*/
public int estimatedSize() {
return cache.size();
}
/**
* Clear the local cache, and, if the database type is set,
* remove the table from the database.
*/
public synchronized void clearAll() {
public void clearAll() {
cache.clear();
// Delete the entire table from the database, if necessary
if (cachedDatabaseType != SonarConfiguration.Database.Type.NONE) {
if (connectionSource != null
&& cachedDatabaseType != SonarConfiguration.Database.Type.NONE) {
try {
dao.deleteBuilder().delete();
} catch (SQLException exception) {
@@ -20,7 +20,6 @@ package xyz.jonesdev.sonar.api.event.impl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.ApiStatus;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
@@ -28,6 +27,5 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
@ToString
@RequiredArgsConstructor
public final class UserBlacklistedEvent implements SonarEvent {
@ApiStatus.Experimental
private final FallbackUser user;
}
@@ -27,6 +27,5 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
@ToString
@RequiredArgsConstructor
public final class UserVerifyJoinEvent implements SonarEvent {
private final String username;
private final FallbackUser user;
}
@@ -22,12 +22,10 @@ import lombok.RequiredArgsConstructor;
import lombok.ToString;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
@Getter
@ToString
@RequiredArgsConstructor
public final class UserVerifySuccessEvent implements SonarEvent {
private final FallbackUser user;
private final SystemTimer loginTimer;
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2024 Sonar 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 xyz.jonesdev.sonar.api.exception;
import org.jetbrains.annotations.NotNull;
public final class AlreadyInitializedException extends RuntimeException {
public AlreadyInitializedException(final @NotNull String message) {
super(message);
}
public Throwable fillInStackTrace() {
return this;
}
}
@@ -18,7 +18,6 @@
package xyz.jonesdev.sonar.api.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.util.ReferenceCountUtil;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
@@ -29,10 +28,7 @@ import java.net.InetAddress;
public interface FallbackUser {
@NotNull
Channel getChannel();
@NotNull
ChannelPipeline getPipeline();
Channel channel();
@NotNull
InetAddress getInetAddress();
@@ -69,8 +65,8 @@ public interface FallbackUser {
* @param msg Message to send to the player
*/
default void write(final @NotNull Object msg) {
if (getChannel().isActive()) {
getChannel().writeAndFlush(msg, getChannel().voidPromise());
if (channel().isActive()) {
channel().writeAndFlush(msg, channel().voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
@@ -81,8 +77,8 @@ public interface FallbackUser {
* sent once all messages are flushed.
*/
default void delayedWrite(final @NotNull Object msg) {
if (getChannel().isActive()) {
getChannel().write(msg, getChannel().voidPromise());
if (channel().isActive()) {
channel().write(msg, channel().voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
@@ -68,7 +68,8 @@ public enum ProtocolVersion {
MINECRAFT_1_20_2(764),
MINECRAFT_1_20_3(765),
MINECRAFT_1_20_5(766),
MINECRAFT_1_21(767);
MINECRAFT_1_21(767),
MINECRAFT_1_21_2(768);
private final int protocol;
@@ -90,6 +91,22 @@ public enum ProtocolVersion {
return ID_TO_PROTOCOL_CONSTANT.getOrDefault(protocol, UNKNOWN);
}
public boolean greaterThan(final ProtocolVersion that) {
return compareTo(that) > 0;
}
public boolean greaterThanOrEquals(final ProtocolVersion that) {
return compareTo(that) >= 0;
}
public boolean lessThan(final ProtocolVersion that) {
return compareTo(that) < 0;
}
public boolean lessThanOrEquals(final ProtocolVersion that) {
return compareTo(that) <= 0;
}
public boolean inBetween(final ProtocolVersion first, final ProtocolVersion last) {
return compareTo(first) >= 0 && compareTo(last) <= 0;
}
@@ -66,7 +66,7 @@ public final class AttackTracker {
// An attack has been detected
if (currentAttack == null) {
currentAttack = new AttackStatistics();
currentAttack.successfulVerifications = Sonar.get().getVerifiedPlayerController().estimatedSize();
currentAttack.successfulVerifications = Sonar.get().getVerifiedPlayerController().getCache().size();
currentAttack.failedVerifications = Sonar.get().getStatistics().getTotalFailedVerifications();
Sonar.get().getEventManager().publish(new AttackDetectedEvent());
Sonar.get().getChatNotificationHandler().handleNotification();
@@ -118,7 +118,7 @@ public final class AttackTracker {
final String endTimestamp = String.valueOf(System.currentTimeMillis() / 1000L);
final long blacklisted = Sonar.get().getFallback().getBlacklist().estimatedSize();
// Calculate during-attack-statistics using their deltas
final long totalVerified = Sonar.get().getVerifiedPlayerController().estimatedSize();
final long totalVerified = Sonar.get().getVerifiedPlayerController().getCache().size();
final long verified = Math.max(totalVerified - currentAttack.successfulVerifications, 0);
final long totalFailed = Sonar.get().getStatistics().getTotalFailedVerifications();
final long failed = Math.max(totalFailed - currentAttack.failedVerifications, 0);
@@ -27,10 +27,9 @@ import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.SonarCommand;
import java.util.Collections;
import java.util.List;
import static java.util.Collections.emptyList;
public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, SonarCommand {
@Override
@@ -54,9 +53,6 @@ public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, S
final String commandAlias,
final String @NotNull [] args) {
// Do not allow tab completion if the player does not have the required permission
if (!sender.hasPermission("sonar.command")) {
return emptyList();
}
return getCachedTabSuggestions(args);
return sender.hasPermission("sonar.command") ? getCachedTabSuggestions(args) : Collections.emptyList();
}
}
@@ -28,18 +28,19 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginStartPacket;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.net.InetSocketAddress;
import java.util.Objects;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INBOUND_HANDLER;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.DEBUG;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
private FallbackPacketRegistry.ProtocolRegistry registry;
FallbackBukkitInboundHandler() {
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
@@ -53,17 +54,13 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
};
}
private FallbackPacketRegistry.ProtocolRegistry registry;
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
public void updateRegistry(final @NotNull FallbackPacketRegistry registry,
final @NotNull ProtocolVersion protocolVersion) {
this.registry = registry.getProtocolRegistry(SERVERBOUND, protocolVersion);
this.registry = registry.getProtocolRegistry(FallbackPacketRegistry.Direction.SERVERBOUND, protocolVersion);
}
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof ByteBuf) {
final ByteBuf byteBuf = (ByteBuf) msg;
@@ -81,7 +78,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
return;
}
final int packetId = readVarInt(byteBuf);
final int packetId = ProtocolUtil.readVarInt(byteBuf);
final FallbackPacket packet = registry.createPacket(packetId);
// Skip the packet if it's not registered within Sonar's packet registry
@@ -95,13 +92,14 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
packet.decode(byteBuf, protocolVersion == null ? DEFAULT_PROTOCOL_VERSION : protocolVersion);
} catch (Throwable throwable) {
byteBuf.release();
throw DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
}
// Check if the packet still has bytes left after we decoded it
if (byteBuf.isReadable()) {
byteBuf.release();
throw DEBUG ? new DecoderException("Could not read packet to end") : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Could not read packet to end ("
+ byteBuf.readableBytes() + " bytes left)") : QuietDecoderException.INSTANCE;
}
/*
@@ -119,12 +117,13 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
case LOGIN:
case TRANSFER:
// Let the actual handler know about the handshake packet
handleHandshake(ctx.channel(), handshake.getHostname(), handshake.getProtocolVersionId());
handleHandshake(ctx, handshake.getHostname(), handshake.getProtocolVersionId());
// Be ready for the next packet (which is supposed to be a login packet)
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
break;
default:
throw DEBUG ? new DecoderException("Bad intent " + handshake.getIntent()) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Bad handshake intent " + handshake.getIntent())
: QuietDecoderException.INSTANCE;
}
} else if (packet instanceof LoginStartPacket) {
final LoginStartPacket loginStart = (LoginStartPacket) packet;
@@ -132,9 +131,10 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Let Sonar process the login packet
handleLogin(ctx.channel(), ctx, () -> {
handleLogin(ctx, () -> {
byteBuf.readerIndex(originalReaderIndex);
ctx.fireChannelRead(byteBuf.retain());
// TODO: recode this?
final ChannelHandler inboundHandler = ctx.pipeline().remove(FALLBACK_INBOUND_HANDLER);
if (inboundHandler != null) {
channelRemovalListener.accept(ctx.pipeline(), FALLBACK_INBOUND_HANDLER, inboundHandler);
@@ -26,8 +26,8 @@ import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -26,7 +26,7 @@ import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.SonarCommand;
import static java.util.Collections.emptyList;
import java.util.Collections;
public final class BungeeSonarCommand extends Command implements TabExecutor, SonarCommand {
public BungeeSonarCommand() {
@@ -47,9 +47,6 @@ public final class BungeeSonarCommand extends Command implements TabExecutor, So
@Override
public Iterable<String> onTabComplete(final @NotNull CommandSender sender, final String @NotNull [] args) {
// Do not allow tab completion if the player does not have the required permission
if (!sender.hasPermission("sonar.command")) {
return emptyList();
}
return getCachedTabSuggestions(args);
return sender.hasPermission("sonar.command") ? getCachedTabSuggestions(args) : Collections.emptyList();
}
}
@@ -17,7 +17,6 @@
package xyz.jonesdev.sonar.bungee.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
@@ -26,8 +25,8 @@ import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.LoginRequest;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -49,42 +48,42 @@ final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
}
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof PacketWrapper) {
final PacketWrapper packetWrapper = (PacketWrapper) msg;
final DefinedPacket wrappedPacket = packetWrapper.packet;
// Skip any unknown or invalid packets
if (wrappedPacket != null) {
final Channel channel = ctx.channel();
// Intercept any handshake packet by the client
if (wrappedPacket instanceof Handshake) {
final Handshake handshake = (Handshake) wrappedPacket;
// We don't care about server pings; remove the handler
if (handshake.getRequestedProtocol() == STATUS) {
ctx.pipeline().remove(this);
} else {
handleHandshake(channel, handshake.getHost(), handshake.getProtocolVersion());
handleHandshake(ctx, handshake.getHost(), handshake.getProtocolVersion());
}
}
// Intercept any server login packet by the client
else if (wrappedPacket instanceof LoginRequest) {
} else if (wrappedPacket instanceof LoginRequest) {
// Make sure to use the potentially modified, real IP
final LoginRequest loginRequest = (LoginRequest) wrappedPacket;
final HandlerBoss handlerBoss = channel.pipeline().get(HandlerBoss.class);
final ChannelWrapper channelWrapper;
try {
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
} catch (final Throwable throwable) {
throw new ReflectiveOperationException(throwable);
final HandlerBoss handlerBoss = ctx.pipeline().get(HandlerBoss.class);
// This might sometimes happen if the client unexpectedly disconnects
if (handlerBoss != null) {
// Get the real IP address of the player through the channel wrapper
final ChannelWrapper channelWrapper;
try {
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
} catch (final Throwable throwable) {
throw new ReflectiveOperationException(throwable);
}
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Make sure to mark this packet as released
packetWrapper.trySingleRelease();
// Let Sonar process the login packet
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
handleLogin(ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
return;
}
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Make sure to mark this packet as released
packetWrapper.trySingleRelease();
// Let Sonar process the login packet
handleLogin(channel, ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
return;
}
}
}
@@ -38,10 +38,6 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import static java.awt.image.BufferedImage.TYPE_3BYTE_BGR;
import static xyz.jonesdev.sonar.captcha.StandardTTFFontProvider.FONTS;
import static xyz.jonesdev.sonar.captcha.StandardTTFFontProvider.STANDARD_FONT_SIZE;
@Getter
@RequiredArgsConstructor
public final class StandardCaptchaGenerator implements CaptchaGenerator {
@@ -92,7 +88,7 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
private @NotNull BufferedImage createBackgroundImage() {
// Create a new image buffer with a 3-byte color spectrum
final BufferedImage image = new BufferedImage(width, height, TYPE_3BYTE_BGR);
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
if (background == null) {
// Fill the entire image with a noise texture
return FBM.filter(image, image);
@@ -128,7 +124,7 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
for (int i = 0; i < answer.length; i++) {
// Create a glyph vector for the character with a random font
final Font font = FONTS[RANDOM.nextInt(FONTS.length)];
final Font font = StandardTTFFontProvider.FONTS[RANDOM.nextInt(StandardTTFFontProvider.FONTS.length)];
glyphs[i] = font.createGlyphVector(ctx, String.valueOf(answer[i]));
}
@@ -139,7 +135,7 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
.mapToDouble(glyph -> glyph.getLogicalBounds().getWidth() * scalingXY - 1)
.sum();
double beginX = Math.max(Math.min(width / 2D - totalWidth / 2D, totalWidth), 0);
double beginY = (height + STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
double beginY = (height + StandardTTFFontProvider.STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
// Draw each glyph one by one
for (final GlyphVector glyph : glyphs) {
@@ -46,7 +46,6 @@ import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.io.File;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@Getter
@RequiredArgsConstructor
@@ -142,7 +141,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
// Update ratelimiter cache
getFallback().setRatelimiter(getConfig().getVerification().getReconnectDelay() > 0L
? new CaffeineCacheRatelimiter(getConfig().getVerification().getReconnectDelay(), TimeUnit.MILLISECONDS)
? new CaffeineCacheRatelimiter(Duration.ofMillis(getConfig().getVerification().getReconnectDelay()))
: NoopCacheRatelimiter.INSTANCE);
// Update blacklist cache
@@ -84,7 +84,7 @@ public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
*/
@Override
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx,
final @NotNull Throwable cause) throws Exception {
final Throwable cause) throws Exception {
ctx.close();
}
}
@@ -25,10 +25,12 @@ import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.fingerprint.FingerprintingUtil;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackTimeoutHandler;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.GeyserUtil;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.net.InetAddress;
@@ -38,7 +40,6 @@ import java.util.concurrent.TimeUnit;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.closeWith;
@AllArgsConstructor
@RequiredArgsConstructor
@@ -49,11 +50,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
/**
* Validates and handles incoming handshake packets
*
* @param hostname Hostname (server address) sent by the client
* @param protocol Protocol version number sent by the client
*/
protected final void handleHandshake(final @NotNull Channel channel,
protected final void handleHandshake(final @NotNull ChannelHandlerContext ctx,
final @NotNull String hostname,
final int protocol) throws Exception {
// Check if the hostname is invalid
@@ -65,20 +63,14 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
throw QuietDecoderException.INSTANCE;
}
protocolVersion = ProtocolVersion.fromId(protocol);
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
ctx.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
}
/**
* Validates and handles incoming login packets
*
* @param channel Forwarded client channel
* @param loginPacket Login packet sent by the client
* @param username Username sent by the client
* @param socketAddress Socket address of the client
*/
protected final void handleLogin(final @NotNull Channel channel,
final @NotNull ChannelHandlerContext ctx,
final @NotNull Runnable loginPacket,
protected final void handleLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull Runnable initialLoginAction,
final @NotNull String username,
final @NotNull InetSocketAddress socketAddress) throws Exception {
// Check if the player has already sent a login packet
@@ -86,7 +78,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
throw QuietDecoderException.INSTANCE;
}
// Connections from unknown protocol versions will be discarded
// as this is the safest way of handling unwanted connections
// as this is the safest way of handling unwanted connections.
// Sonar does not support snapshots or Minecraft versions older than 1.7.2
if (protocolVersion.isUnknown()) {
throw QuietDecoderException.INSTANCE;
@@ -95,17 +87,17 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
this.username = username;
final InetAddress inetAddress = socketAddress.getAddress();
channel.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
ctx.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
// Check if Fallback is already verifying a player with the same IP address
if (Sonar.get().getFallback().getConnected().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyVerifying);
customDisconnect(ctx.channel(), alreadyVerifying, protocolVersion);
return;
}
// Check if the protocol ID of the player is not allowed to enter the server
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols().contains(protocolVersion.getProtocol())) {
customDisconnect(channel, protocolVersion, protocolBlacklisted);
customDisconnect(ctx.channel(), protocolBlacklisted, protocolVersion);
return;
}
@@ -115,34 +107,34 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
if (limit > 0) {
final int score = Sonar.get().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
if (score >= limit) {
customDisconnect(channel, protocolVersion, blacklisted);
customDisconnect(ctx.channel(), blacklisted, protocolVersion);
return;
}
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Completely skip Geyser connections if configured
final boolean geyser = GeyserUtil.isGeyserConnection(channel, socketAddress);
final boolean geyser = GeyserUtil.isGeyserConnection(ctx.channel(), socketAddress);
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Make sure we actually have to verify the player
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Check if the IP address is currently being rate-limited
if (!Sonar.get().getFallback().getRatelimiter().attempt(inetAddress)) {
customDisconnect(channel, protocolVersion, reconnectedTooFast);
customDisconnect(ctx.channel(), reconnectedTooFast, protocolVersion);
return;
}
@@ -150,10 +142,10 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
rewriteProtocol(ctx, channelRemovalListener);
// Queue the connection for further processing
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, action) -> {
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
// Check if the player is already queued since we don't want bots to flood the queue
if (action != null) {
customDisconnect(channel, protocolVersion, alreadyQueued);
if (runnable != null) {
customDisconnect(ctx.channel(), alreadyQueued, protocolVersion);
// Remove other instances of this IP address from the queue
return null;
}
@@ -162,16 +154,41 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
// Check if the username matches the valid name regex to prevent
// UTF-16 names or other types of exploits
if (!Sonar.get().getConfig().getVerification().getValidNameRegex().matcher(username).matches()) {
customDisconnect(channel, protocolVersion, invalidUsername);
customDisconnect(ctx.channel(), invalidUsername, protocolVersion);
return;
}
// Create an instance for the user and let the verification handler take over the channel
new FallbackUserWrapper(channel, inetAddress, protocolVersion, username, fingerprint, geyser);
new FallbackUserWrapper(ctx.channel(), inetAddress, protocolVersion, username, fingerprint, geyser);
};
});
}
/**
* Executes the maximum accounts per IP limit check before letting the player join
*/
protected final void initialLogin(final @NotNull Channel channel,
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (count >= maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return count + 1;
}
}
// Let the server know about the login packet
loginPacket.run();
// Increment the number of accounts with the same IP
return count == null ? 1 : count + 1;
});
}
/**
* Removes all pipelines and rewrites them using our custom handlers
*/
@@ -196,58 +213,24 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
TimeUnit.MILLISECONDS));
}
/**
* Executes the maximum accounts per IP limit check before letting the player join
*
* @param ctx Forwarded client channel context
* @param inetAddress Resolved client IP address
* @param loginPacket Login packet sent by the client
*/
protected final void initialLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (count >= maxOnlinePerIp) {
customDisconnect(ctx.channel(), protocolVersion, tooManyOnlinePerIP);
return count + 1;
}
}
// Let the server know about the login packet
loginPacket.run();
// Increment the number of accounts with the same IP
return count == null ? 1 : count + 1;
});
}
/**
* Disconnect the player before verification (during login)
* by replacing the encoder before running the method.
*
* @param packet Disconnect packet
* @param channel Channel of the player
* @param protocolVersion Protocol version of the player
*/
private void customDisconnect(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet) {
private static void customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final @NotNull ProtocolVersion protocolVersion) {
if (channel.isActive()) {
if (channel.eventLoop().inEventLoop()) {
_customDisconnect(channel, protocolVersion, packet);
_customDisconnect(channel, packet, protocolVersion);
} else {
channel.eventLoop().execute(() -> _customDisconnect(channel, protocolVersion, packet));
channel.eventLoop().execute(() -> _customDisconnect(channel, packet, protocolVersion));
}
}
}
private void _customDisconnect(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet) {
/**
* Disconnect the player before verification (during login)
*/
private static void _customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final @NotNull ProtocolVersion protocolVersion) {
// Remove the connection handler pipeline to completely take over the channel
final String handler = Sonar.get().getPlatform().getConnectionHandler();
if (channel.pipeline().context(handler) != null) {
@@ -262,7 +245,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
channel.pipeline().replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
}
closeWith(channel, protocolVersion, packet);
ProtocolUtil.closeWith(channel, protocolVersion, packet);
} else {
channel.close();
}
@@ -18,10 +18,10 @@
package xyz.jonesdev.sonar.common.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
@@ -34,24 +34,22 @@ import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarIntLengthEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.DisconnectPacket;
import xyz.jonesdev.sonar.common.fallback.verification.FallbackPreJoinHandler;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.net.InetAddress;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_2;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.CONFIG;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.loginSuccess;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.closeWith;
@Getter
@ToString(of = {"protocolVersion", "inetAddress", "geyser"})
public final class FallbackUserWrapper implements FallbackUser {
@Accessors(fluent = true)
private final Channel channel;
private final ChannelPipeline pipeline;
private final InetAddress inetAddress;
private final ProtocolVersion protocolVersion;
private final String fingerprint;
@@ -68,7 +66,6 @@ public final class FallbackUserWrapper implements FallbackUser {
final @NotNull String fingerprint,
final boolean geyser) {
this.channel = channel;
this.pipeline = channel.pipeline();
this.inetAddress = inetAddress;
this.protocolVersion = protocolVersion;
this.username = username;
@@ -89,7 +86,7 @@ public final class FallbackUserWrapper implements FallbackUser {
}
// Call the VerifyJoinEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(username, this));
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(this));
// Run this in the channel's event loop to avoid issues
channel.eventLoop().execute(() -> {
@@ -102,32 +99,42 @@ public final class FallbackUserWrapper implements FallbackUser {
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
// Replace normal encoder to allow custom packets
// TODO: recode injection
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Send LoginSuccess packet to make the client think they are joining the server
write(loginSuccess);
// The LoginSuccess packet has been sent, now we can change the registry state
newEncoder.updateRegistry(protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0 ? CONFIG : GAME);
channel.pipeline().addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
channel.pipeline().addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Replace normal decoder to allow custom packets
// TODO: recode injection
final FallbackPacketDecoder newDecoder = new FallbackPacketDecoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
pipeline.addLast(FALLBACK_PACKET_DECODER, newDecoder);
channel.pipeline().addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
channel.pipeline().addLast(FALLBACK_PACKET_DECODER, newDecoder);
// We're sending the LoginSuccess packet now
newDecoder.updateRegistry(FallbackPacketRegistry.LOGIN);
newEncoder.updateRegistry(FallbackPacketRegistry.LOGIN);
// Send LoginSuccess packet to make the client think they are joining the server
write(FallbackPreparer.loginSuccess);
// pre-1.20.2 clients do not have the configuration stage
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
newDecoder.updateRegistry(FallbackPacketRegistry.GAME);
newEncoder.updateRegistry(FallbackPacketRegistry.GAME);
}
// Listen for all incoming packets by setting the packet listener
newDecoder.setListener(new FallbackPreJoinHandler(this));
// Make sure to catch all exceptions during the verification
pipeline.addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
// TODO: recode injection
channel.pipeline().addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
});
}
@Override
public void disconnect(final @NotNull Component reason) {
final FallbackPacketEncoder encoder = pipeline.get(FallbackPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() != GAME;
closeWith(channel, protocolVersion, DisconnectPacket.create(reason, duringLogin));
final FallbackPacketEncoder encoder = channel.pipeline().get(FallbackPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() == FallbackPacketRegistry.LOGIN;
ProtocolUtil.closeWith(channel, protocolVersion, new DisconnectPacket(reason, duringLogin));
}
}
@@ -24,9 +24,9 @@ import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandler;
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -31,7 +31,7 @@ public final class FallbackTailExceptionsHandler extends ChannelDuplexHandler {
// Additionally, this will also run after our custom decoder.
@Override
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx,
final @NotNull Throwable cause) throws Exception {
final Throwable cause) throws Exception {
// Close the channel if we encounter any errors.
ctx.close();
}
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback;
package xyz.jonesdev.sonar.common.fallback.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
@@ -26,12 +26,12 @@ import java.util.concurrent.TimeUnit;
public final class FallbackTimeoutHandler extends IdleStateHandler {
public FallbackTimeoutHandler(final int readTimeout, final int writeTimeout, final TimeUnit timeUnit) {
super(readTimeout, writeTimeout, 0L, timeUnit);
super(false, readTimeout, writeTimeout, 0L, timeUnit);
}
@Override
protected void channelIdle(final @NotNull ChannelHandlerContext ctx,
final @NotNull IdleStateEvent idleStateEvent) throws Exception {
final IdleStateEvent idleStateEvent) throws Exception {
// The netty (default) ReadTimeoutHandler would normally just throw an Exception
// The default ReadTimeoutHandler does only check for the boolean 'closed' and
// still throws the Exception even if the channel is closed
@@ -21,14 +21,13 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.DecoderException;
import io.netty.util.ByteProcessor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.util.List;
import static io.netty.util.ByteProcessor.FIND_NON_NUL;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.DEBUG;
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java
public final class FallbackVarInt21FrameDecoder extends ByteToMessageDecoder {
@@ -42,7 +41,7 @@ public final class FallbackVarInt21FrameDecoder extends ByteToMessageDecoder {
}
// Skip any runs of 0x00 we might find
final int packetStart = byteBuf.forEachByte(FIND_NON_NUL);
final int packetStart = byteBuf.forEachByte(ByteProcessor.FIND_NON_NUL);
if (packetStart == -1) {
return;
}
@@ -56,7 +55,7 @@ public final class FallbackVarInt21FrameDecoder extends ByteToMessageDecoder {
return;
}
if (length <= 0) {
throw DEBUG ? new DecoderException("Bad VarInt length: " + length) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Bad VarInt length: " + length) : QuietDecoderException.INSTANCE;
}
if (byteBuf.readableBytes() < length) {
@@ -75,7 +74,7 @@ public final class FallbackVarInt21FrameDecoder extends ByteToMessageDecoder {
final int wholeOrMore = byteBuf.getIntLE(byteBuf.readerIndex());
final int atStop = ~wholeOrMore & 0x808080;
if (atStop == 0) {
throw DEBUG ? new DecoderException("VarInt too big") : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("VarInt too big") : QuietDecoderException.INSTANCE;
}
final int bitsToKeep = Integer.numberOfTrailingZeros(atStop) + 1;
@@ -22,12 +22,10 @@ import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.util.List;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.varIntBytes;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java
@ChannelHandler.Sharable
public final class FallbackVarIntLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
@@ -37,12 +35,12 @@ public final class FallbackVarIntLengthEncoder extends MessageToMessageEncoder<B
protected void encode(final @NotNull ChannelHandlerContext ctx,
final @NotNull ByteBuf byteBuf,
final @NotNull List<Object> out) throws Exception {
final int length = byteBuf.readableBytes();
final int varIntLength = varIntBytes(length);
final int readableBytes = byteBuf.readableBytes();
final int length = ProtocolUtil.varIntBytes(readableBytes);
final ByteBuf lenBuf = ctx.alloc().buffer(varIntLength);
final ByteBuf lenBuf = ctx.alloc().buffer(length);
writeVarInt(lenBuf, length);
ProtocolUtil.writeVarInt(lenBuf, readableBytes);
out.add(lenBuf);
out.add(byteBuf.retain());
}
@@ -15,12 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
package xyz.jonesdev.sonar.common.fallback.protocol;
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.event.impl.CaptchaGenerationEndEvent;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.fallback.protocol.map.MapCaptchaInfo;
import xyz.jonesdev.sonar.common.fallback.protocol.map.MapColorPalette;
import java.awt.image.BufferedImage;
import java.util.Random;
@@ -39,20 +39,18 @@ public interface FallbackPacket {
void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception;
/**
* @param byteBuf ByteBuf
* @param protocolVersion Protocol version of the player
* @return The minimum allowed length of the decoded packet
*/
default int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
default int expectedMinLength(final ProtocolVersion protocolVersion) {
return -1;
}
/**
* @param byteBuf ByteBuf
* @param protocolVersion Protocol version of the player
* @return The maximum allowed length of the decoded packet
*/
default int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
default int expectedMaxLength(final ProtocolVersion protocolVersion) {
return -1;
}
}
@@ -21,19 +21,16 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.DecoderException;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_2;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.LOGIN;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.maxTotalPacketsSent;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.DEBUG;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
@RequiredArgsConstructor
public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
private final ProtocolVersion protocolVersion;
private FallbackPacketRegistry.ProtocolRegistry registry;
@@ -41,18 +38,12 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
private FallbackPacketListener listener;
private int totalPacketsSent;
public FallbackPacketDecoder(final @NotNull ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
updateRegistry(protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0 ? LOGIN : GAME);
}
public void updateRegistry(final @NotNull FallbackPacketRegistry registry) {
this.registry = registry.getProtocolRegistry(SERVERBOUND, protocolVersion);
this.registry = registry.getProtocolRegistry(FallbackPacketRegistry.Direction.SERVERBOUND, protocolVersion);
}
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx,
final @NotNull Object msg) throws Exception {
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof ByteBuf) {
final ByteBuf byteBuf = (ByteBuf) msg;
@@ -66,11 +57,11 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
// Don't allow the player to spam packets to overload netty
if (++totalPacketsSent > maxTotalPacketsSent) {
throw DEBUG ? new DecoderException("Sent too many packets") : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Sent too many packets") : QuietDecoderException.INSTANCE;
}
// Read the packet ID and then create the packet from it
final int packetId = readVarInt(byteBuf);
final int packetId = ProtocolUtil.readVarInt(byteBuf);
final FallbackPacket packet = registry.createPacket(packetId);
// Skip the packet if it's not registered within Sonar's packet registry
@@ -79,18 +70,19 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
}
// Ensure that the packet isn't too large or too small
checkPacketSize(byteBuf, packet);
checkPacketSize(byteBuf.readableBytes(), packet);
try {
// Try to decode the packet for the given protocol version
packet.decode(byteBuf, protocolVersion);
} catch (Throwable throwable) {
throw DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
}
// Check if the packet still has bytes left after we decoded it
if (byteBuf.isReadable()) {
throw DEBUG ? new DecoderException("Could not read packet to end") : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Could not read packet to end ("
+ byteBuf.readableBytes() + " bytes left)"): QuietDecoderException.INSTANCE;
}
// Let our verification handler process the packet
@@ -106,16 +98,16 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
}
}
private void checkPacketSize(final @NotNull ByteBuf byteBuf,
final @NotNull FallbackPacket packet) throws Exception {
final int packetSize = byteBuf.readableBytes();
final int expectedMaxLen = packet.expectedMaxLength(byteBuf, protocolVersion);
private void checkPacketSize(final int packetSize, final @NotNull FallbackPacket packet) throws Exception {
final int expectedMaxLen = packet.expectedMaxLength(protocolVersion);
if (expectedMaxLen != -1 && packetSize > expectedMaxLen) {
throw DEBUG ? new DecoderException("Packet too large: " + packetSize) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Packet too large: " + packetSize + " max: " + expectedMaxLen)
: QuietDecoderException.INSTANCE;
}
final int expectedMinLen = packet.expectedMinLength(byteBuf, protocolVersion);
final int expectedMinLen = packet.expectedMinLength(protocolVersion);
if (expectedMinLen != -1 && packetSize < expectedMinLen) {
throw DEBUG ? new DecoderException("Packet too small: " + packetSize) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Packet too small: " + packetSize + " min: " + expectedMinLen)
: QuietDecoderException.INSTANCE;
}
}
}
@@ -21,37 +21,31 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.CLIENTBOUND;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.LOGIN;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@RequiredArgsConstructor
public final class FallbackPacketEncoder extends MessageToByteEncoder<FallbackPacket> {
private final ProtocolVersion protocolVersion;
@Getter
private FallbackPacketRegistry packetRegistry;
private FallbackPacketRegistry.ProtocolRegistry protocolRegistry;
public FallbackPacketEncoder(final ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
updateRegistry(LOGIN);
}
public void updateRegistry(final @NotNull FallbackPacketRegistry packetRegistry) {
this.packetRegistry = packetRegistry;
this.protocolRegistry = packetRegistry.getProtocolRegistry(CLIENTBOUND, protocolVersion);
this.protocolRegistry = packetRegistry.getProtocolRegistry(FallbackPacketRegistry.Direction.CLIENTBOUND, protocolVersion);
}
@Override
protected void encode(final ChannelHandlerContext ctx,
final @NotNull FallbackPacket packet,
final ByteBuf out) throws Exception {
final FallbackPacket originalPacket = packet instanceof FallbackPacketSnapshot
? ((FallbackPacketSnapshot) packet).getOriginalPacket() : packet;
final int packetId = protocolRegistry.getPacketId(originalPacket.getClass());
writeVarInt(out, packetId);
final @NotNull ByteBuf out) throws Exception {
final Class<? extends FallbackPacket> originalPacket = packet instanceof FallbackPacketSnapshot
? ((FallbackPacketSnapshot) packet).getOriginalPacketClass() : packet.getClass();
final int packetId = protocolRegistry.getPacketId(originalPacket);
ProtocolUtil.writeVarInt(out, packetId);
packet.encode(out, protocolVersion);
}
}
@@ -105,7 +105,8 @@ public enum FallbackPacketRegistry {
map(0x24, MINECRAFT_1_19_3, true),
map(0x28, MINECRAFT_1_19_4, true),
map(0x29, MINECRAFT_1_20_2, true),
map(0x2B, MINECRAFT_1_20_5, true));
map(0x2B, MINECRAFT_1_20_5, true),
map(0x2C, MINECRAFT_1_21_2, true));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, true),
map(0x1F, MINECRAFT_1_9, true),
@@ -120,7 +121,8 @@ public enum FallbackPacketRegistry {
map(0x1F, MINECRAFT_1_19_3, true),
map(0x23, MINECRAFT_1_19_4, true),
map(0x24, MINECRAFT_1_20_2, true),
map(0x26, MINECRAFT_1_20_5, true));
map(0x26, MINECRAFT_1_20_5, true),
map(0x27, MINECRAFT_1_21_2, true));
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x40, MINECRAFT_1_7_2, true),
map(0x1A, MINECRAFT_1_9, true),
@@ -151,7 +153,8 @@ public enum FallbackPacketRegistry {
map(0x38, MINECRAFT_1_19_3, true),
map(0x3C, MINECRAFT_1_19_4, true),
map(0x3E, MINECRAFT_1_20_2, true),
map(0x40, MINECRAFT_1_20_5, true));
map(0x40, MINECRAFT_1_20_5, true),
map(0x42, MINECRAFT_1_21_2, true));
clientbound.register(PlayerAbilitiesPacket.class, PlayerAbilitiesPacket::new,
map(0x39, MINECRAFT_1_7_2, true),
map(0x2B, MINECRAFT_1_9, true),
@@ -167,7 +170,8 @@ public enum FallbackPacketRegistry {
map(0x30, MINECRAFT_1_19_3, true),
map(0x34, MINECRAFT_1_19_4, true),
map(0x36, MINECRAFT_1_20_2, true),
map(0x38, MINECRAFT_1_20_5, true));
map(0x38, MINECRAFT_1_20_5, true),
map(0x3A, MINECRAFT_1_21_2, true));
clientbound.register(ChunkDataPacket.class, ChunkDataPacket::new,
map(0x21, MINECRAFT_1_7_2, true),
map(0x20, MINECRAFT_1_9, true),
@@ -182,7 +186,8 @@ public enum FallbackPacketRegistry {
map(0x20, MINECRAFT_1_19_3, true),
map(0x24, MINECRAFT_1_19_4, true),
map(0x25, MINECRAFT_1_20_2, true),
map(0x27, MINECRAFT_1_20_5, true));
map(0x27, MINECRAFT_1_20_5, true),
map(0x28, MINECRAFT_1_21_2, true));
clientbound.register(RemoveEntitiesPacket.class, RemoveEntitiesPacket::new,
map(0x13, MINECRAFT_1_7_2, true),
map(0x30, MINECRAFT_1_9, true),
@@ -200,7 +205,8 @@ public enum FallbackPacketRegistry {
map(0x3E, MINECRAFT_1_19_4, true),
map(0x3E, MINECRAFT_1_19_4, true),
map(0x40, MINECRAFT_1_20_2, true),
map(0x42, MINECRAFT_1_20_5, true));
map(0x42, MINECRAFT_1_20_5, true),
map(0x47, MINECRAFT_1_21_2, true));
clientbound.register(SetPassengersPacket.class, SetPassengersPacket::new,
map(0x1B, MINECRAFT_1_7_2, true),
map(0x40, MINECRAFT_1_9, true),
@@ -215,7 +221,8 @@ public enum FallbackPacketRegistry {
map(0x59, MINECRAFT_1_19_4, true),
map(0x5B, MINECRAFT_1_20_2, true),
map(0x5D, MINECRAFT_1_20_3, true),
map(0x5F, MINECRAFT_1_20_5, true));
map(0x5F, MINECRAFT_1_20_5, true),
map(0x65, MINECRAFT_1_21_2, true));
clientbound.register(VehicleMovePacket.class, VehicleMovePacket::new,
map(0x29, MINECRAFT_1_9, true),
map(0x2B, MINECRAFT_1_13, true),
@@ -229,7 +236,8 @@ public enum FallbackPacketRegistry {
map(0x2A, MINECRAFT_1_19_3, true),
map(0x2E, MINECRAFT_1_19_4, true),
map(0x2F, MINECRAFT_1_20_2, true),
map(0x31, MINECRAFT_1_20_5, true));
map(0x31, MINECRAFT_1_20_5, true),
map(0x33, MINECRAFT_1_21_2, true));
clientbound.register(SpawnEntityPacket.class, SpawnEntityPacket::new,
map(0x0E, MINECRAFT_1_7_2, true),
map(0x00, MINECRAFT_1_9, true),
@@ -248,7 +256,8 @@ public enum FallbackPacketRegistry {
map(0x43, MINECRAFT_1_19_4, true),
map(0x45, MINECRAFT_1_20_2, true),
map(0x47, MINECRAFT_1_20_3, true),
map(0x49, MINECRAFT_1_20_5, true));
map(0x49, MINECRAFT_1_20_5, true),
map(0x4E, MINECRAFT_1_21_2, true));
clientbound.register(TransactionPacket.class, TransactionPacket::new,
map(0x32, MINECRAFT_1_7_2, true),
map(0x11, MINECRAFT_1_9, true),
@@ -262,7 +271,8 @@ public enum FallbackPacketRegistry {
map(0x2E, MINECRAFT_1_19_3, true),
map(0x32, MINECRAFT_1_19_4, true),
map(0x33, MINECRAFT_1_20_2, true),
map(0x35, MINECRAFT_1_20_5, true));
map(0x35, MINECRAFT_1_20_5, true),
map(0x37, MINECRAFT_1_21_2, true));
clientbound.register(SetDefaultSpawnPositionPacket.class, SetDefaultSpawnPositionPacket::new,
map(0x05, MINECRAFT_1_7_2, true),
map(0x43, MINECRAFT_1_9, true),
@@ -279,7 +289,8 @@ public enum FallbackPacketRegistry {
map(0x50, MINECRAFT_1_19_4, true),
map(0x52, MINECRAFT_1_20_2, true),
map(0x54, MINECRAFT_1_20_3, true),
map(0x56, MINECRAFT_1_20_5, true));
map(0x56, MINECRAFT_1_20_5, true),
map(0x5B, MINECRAFT_1_21_2, true));
clientbound.register(MapDataPacket.class, MapDataPacket::new,
map(0x34, MINECRAFT_1_7_2, true),
map(0x24, MINECRAFT_1_9, true),
@@ -293,7 +304,8 @@ public enum FallbackPacketRegistry {
map(0x25, MINECRAFT_1_19_3, true),
map(0x29, MINECRAFT_1_19_4, true),
map(0x2A, MINECRAFT_1_20_2, true),
map(0x2C, MINECRAFT_1_20_5, true));
map(0x2C, MINECRAFT_1_20_5, true),
map(0x2D, MINECRAFT_1_21_2, true));
clientbound.register(SetContainerSlotPacket.class, SetContainerSlotPacket::new,
map(0x2F, MINECRAFT_1_7_2, true),
map(0x16, MINECRAFT_1_9, true),
@@ -321,7 +333,8 @@ public enum FallbackPacketRegistry {
map(0x56, MINECRAFT_1_19_4, true),
map(0x58, MINECRAFT_1_20_2, true),
map(0x5A, MINECRAFT_1_20_3, true),
map(0x5C, MINECRAFT_1_20_5, true));
map(0x5C, MINECRAFT_1_20_5, true),
map(0x61, MINECRAFT_1_21_2, true));
clientbound.register(SystemChatPacket.class, SystemChatPacket::new,
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
@@ -335,12 +348,15 @@ public enum FallbackPacketRegistry {
map(0x64, MINECRAFT_1_19_4, true),
map(0x67, MINECRAFT_1_20_2, true),
map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true));
map(0x6C, MINECRAFT_1_20_5, true),
map(0x73, MINECRAFT_1_21_2, true));
clientbound.register(GameEventPacket.class, GameEventPacket::new,
map(0x20, MINECRAFT_1_20_3, true),
map(0x22, MINECRAFT_1_20_5, true));
map(0x22, MINECRAFT_1_20_5, true),
map(0x23, MINECRAFT_1_21_2, true));
clientbound.register(TransferPacket.class, TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, true));
map(0x73, MINECRAFT_1_20_5, true),
map(0x7A, MINECRAFT_1_21_2, true));
clientbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
map(0x09, MINECRAFT_1_7_2, true),
map(0x37, MINECRAFT_1_9, true),
@@ -357,7 +373,8 @@ public enum FallbackPacketRegistry {
map(0x4D, MINECRAFT_1_19_4, true),
map(0x4F, MINECRAFT_1_20_2, true),
map(0x51, MINECRAFT_1_20_3, true),
map(0x53, MINECRAFT_1_20_5, true));
map(0x53, MINECRAFT_1_20_5, true),
map(0x63, MINECRAFT_1_21_2, true));
clientbound.register(EntityAnimationPacket.class, EntityAnimationPacket::new,
map(0x0B, MINECRAFT_1_7_2, true),
map(0x06, MINECRAFT_1_9, true),
@@ -384,7 +401,26 @@ public enum FallbackPacketRegistry {
map(0x68, MINECRAFT_1_19_4, true),
map(0x6B, MINECRAFT_1_20_2, true),
map(0x6D, MINECRAFT_1_20_3, true),
map(0x70, MINECRAFT_1_20_5, true));
map(0x70, MINECRAFT_1_20_5, true),
map(0x77, MINECRAFT_1_21_2, true));
clientbound.register(UpdateTimePacket.class, UpdateTimePacket::new,
map(0x03, MINECRAFT_1_7_2, true),
map(0x44, MINECRAFT_1_9, true),
map(0x46, MINECRAFT_1_12, true),
map(0x47, MINECRAFT_1_12_1, true),
map(0x4A, MINECRAFT_1_13, true),
map(0x4E, MINECRAFT_1_14, true),
map(0x4F, MINECRAFT_1_15, true),
map(0x4E, MINECRAFT_1_16, true),
map(0x58, MINECRAFT_1_17, true),
map(0x59, MINECRAFT_1_18, true),
map(0x5C, MINECRAFT_1_19_1, true),
map(0x5A, MINECRAFT_1_19_3, true),
map(0x5E, MINECRAFT_1_19_4, true),
map(0x60, MINECRAFT_1_20_2, true),
map(0x62, MINECRAFT_1_20_3, true),
map(0x64, MINECRAFT_1_20_5, true),
map(0x6B, MINECRAFT_1_21_2, true));
serverbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
map(0x09, MINECRAFT_1_7_2, false),
@@ -398,7 +434,8 @@ public enum FallbackPacketRegistry {
map(0x28, MINECRAFT_1_19_1, false),
map(0x2B, MINECRAFT_1_20_2, false),
map(0x2C, MINECRAFT_1_20_3, false),
map(0x2F, MINECRAFT_1_20_5, false));
map(0x2F, MINECRAFT_1_20_5, false),
map(0x31, MINECRAFT_1_21_2, false));
serverbound.register(PaddleBoatPacket.class, PaddleBoatPacket::new,
map(0x11, MINECRAFT_1_9, false),
map(0x12, MINECRAFT_1_12, false),
@@ -413,7 +450,8 @@ public enum FallbackPacketRegistry {
map(0x19, MINECRAFT_1_19_4, false),
map(0x1B, MINECRAFT_1_20_2, false),
map(0x1C, MINECRAFT_1_20_3, false),
map(0x1F, MINECRAFT_1_20_5, false));
map(0x1F, MINECRAFT_1_20_5, false),
map(0x21, MINECRAFT_1_21_2, false));
serverbound.register(PlayerInputPacket.class, PlayerInputPacket::new,
map(0x0C, MINECRAFT_1_7_2, false),
map(0x15, MINECRAFT_1_9, false),
@@ -428,7 +466,8 @@ public enum FallbackPacketRegistry {
map(0x1F, MINECRAFT_1_19_4, false),
map(0x22, MINECRAFT_1_20_2, false),
map(0x23, MINECRAFT_1_20_3, false),
map(0x26, MINECRAFT_1_20_5, false));
map(0x26, MINECRAFT_1_20_5, false),
map(0x28, MINECRAFT_1_21_2, false));
serverbound.register(VehicleMovePacket.class, VehicleMovePacket::new,
map(0x10, MINECRAFT_1_9, false),
map(0x11, MINECRAFT_1_12, false),
@@ -443,7 +482,8 @@ public enum FallbackPacketRegistry {
map(0x18, MINECRAFT_1_19_4, false),
map(0x1A, MINECRAFT_1_20_2, false),
map(0x1B, MINECRAFT_1_20_3, false),
map(0x1E, MINECRAFT_1_20_5, false));
map(0x1E, MINECRAFT_1_20_5, false),
map(0x20, MINECRAFT_1_21_2, false));
serverbound.register(SystemChatPacket.class, SystemChatPacket::new,
map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false),
@@ -453,7 +493,8 @@ public enum FallbackPacketRegistry {
map(0x04, MINECRAFT_1_19, false),
map(0x05, MINECRAFT_1_19_1, false),
map(0x05, MINECRAFT_1_19_3, false),
map(0x06, MINECRAFT_1_20_5, false));
map(0x06, MINECRAFT_1_20_5, false),
map(0x07, MINECRAFT_1_21_2, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false),
@@ -469,7 +510,8 @@ public enum FallbackPacketRegistry {
map(0x12, MINECRAFT_1_19_4, false),
map(0x14, MINECRAFT_1_20_2, false),
map(0x15, MINECRAFT_1_20_3, false),
map(0x18, MINECRAFT_1_20_5, false));
map(0x18, MINECRAFT_1_20_5, false),
map(0x1A, MINECRAFT_1_21_2, false));
serverbound.register(ClientInformationPacket.class, ClientInformationPacket::new,
map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false),
@@ -481,7 +523,8 @@ public enum FallbackPacketRegistry {
map(0x07, MINECRAFT_1_19_3, false),
map(0x08, MINECRAFT_1_19_4, false),
map(0x09, MINECRAFT_1_20_2, false),
map(0x0A, MINECRAFT_1_20_5, false));
map(0x0A, MINECRAFT_1_20_5, false),
map(0xC, MINECRAFT_1_21_2, false));
serverbound.register(PluginMessagePacket.class, PluginMessagePacket::new,
map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false),
@@ -495,7 +538,8 @@ public enum FallbackPacketRegistry {
map(0x0C, MINECRAFT_1_19_3, false),
map(0x0D, MINECRAFT_1_19_4, false),
map(0x0F, MINECRAFT_1_20_2, false),
map(0x12, MINECRAFT_1_21, false));
map(0x12, MINECRAFT_1_21, false),
map(0x14, MINECRAFT_1_21_2, false));
serverbound.register(SetPlayerOnGround.class, SetPlayerOnGround::new,
map(0x03, MINECRAFT_1_7_2, false),
map(0x0F, MINECRAFT_1_9, false),
@@ -511,7 +555,8 @@ public enum FallbackPacketRegistry {
map(0x17, MINECRAFT_1_19_4, false),
map(0x19, MINECRAFT_1_20_2, false),
map(0x1A, MINECRAFT_1_20_3, false),
map(0x1D, MINECRAFT_1_20_5, false));
map(0x1D, MINECRAFT_1_20_5, false),
map(0x1F, MINECRAFT_1_21_2, false));
serverbound.register(SetPlayerPositionPacket.class, SetPlayerPositionPacket::new,
map(0x04, MINECRAFT_1_7_2, false),
map(0x0C, MINECRAFT_1_9, false),
@@ -527,7 +572,8 @@ public enum FallbackPacketRegistry {
map(0x14, MINECRAFT_1_19_4, false),
map(0x16, MINECRAFT_1_20_2, false),
map(0x17, MINECRAFT_1_20_3, false),
map(0x1A, MINECRAFT_1_20_5, false));
map(0x1A, MINECRAFT_1_20_5, false),
map(0x1C, MINECRAFT_1_21_2, false));
serverbound.register(SetPlayerRotationPacket.class, SetPlayerRotationPacket::new,
map(0x05, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false),
@@ -543,7 +589,8 @@ public enum FallbackPacketRegistry {
map(0x16, MINECRAFT_1_19_4, false),
map(0x18, MINECRAFT_1_20_2, false),
map(0x19, MINECRAFT_1_20_3, false),
map(0x1C, MINECRAFT_1_20_5, false));
map(0x1C, MINECRAFT_1_20_5, false),
map(0x1E, MINECRAFT_1_21_2, false));
serverbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
map(0x06, MINECRAFT_1_7_2, false),
map(0x0D, MINECRAFT_1_9, false),
@@ -559,7 +606,8 @@ public enum FallbackPacketRegistry {
map(0x15, MINECRAFT_1_19_4, false),
map(0x17, MINECRAFT_1_20_2, false),
map(0x18, MINECRAFT_1_20_3, false),
map(0x1B, MINECRAFT_1_20_5, false));
map(0x1B, MINECRAFT_1_20_5, false),
map(0x1D, MINECRAFT_1_21_2, false));
serverbound.register(ConfirmTeleportationPacket.class, ConfirmTeleportationPacket::new,
map(0x00, MINECRAFT_1_9, false));
serverbound.register(TransactionPacket.class, TransactionPacket::new,
@@ -576,7 +624,8 @@ public enum FallbackPacketRegistry {
map(0x20, MINECRAFT_1_19_4, false),
map(0x23, MINECRAFT_1_20_2, false),
map(0x24, MINECRAFT_1_20_3, false),
map(0x27, MINECRAFT_1_20_5, false));
map(0x27, MINECRAFT_1_20_5, false),
map(0x29, MINECRAFT_1_21_2, false));
serverbound.register(AnimationPacket.class, AnimationPacket::new,
map(0x0A, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false),
@@ -589,7 +638,8 @@ public enum FallbackPacketRegistry {
map(0x2F, MINECRAFT_1_19_1, false),
map(0x32, MINECRAFT_1_20_2, false),
map(0x33, MINECRAFT_1_20_3, false),
map(0x36, MINECRAFT_1_20_5, false));
map(0x36, MINECRAFT_1_20_5, false),
map(0x38, MINECRAFT_1_21_2, false));
}
};
@@ -19,70 +19,74 @@ package xyz.jonesdev.sonar.common.fallback.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.EncoderException;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.HashMap;
import java.util.Map;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.ID_TO_PROTOCOL_CONSTANT;
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/PacketSnapshot.java
@Getter
public final class FallbackPacketSnapshot implements FallbackPacket {
private final Map<Integer, Integer> mappings = new HashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
private final Map<Integer, byte[]> cachedBytes = new HashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
private final FallbackPacket originalPacket;
private final IntObjectMap<Integer> mappings = new IntObjectHashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
private final IntObjectMap<byte[]> cachedBytes = new IntObjectHashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
@Getter
private final Class<? extends FallbackPacket> originalPacketClass;
public FallbackPacketSnapshot(final @NotNull FallbackPacket originalPacket) {
final Map<Integer, Integer> hashes = new HashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
this.originalPacketClass = originalPacket.getClass();
final IntObjectMap<Integer> hashedData = new IntObjectHashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
for (final ProtocolVersion protocolVersion : ID_TO_PROTOCOL_CONSTANT.values()) {
// Allocate a buffer for each protocol version
final ByteBuf byteBuf = Unpooled.buffer();
try {
originalPacket.encode(byteBuf, protocolVersion);
} catch (Exception exception) {
Sonar.get().getLogger().error("Could not encode packet {} for version {}: {}",
originalPacket.toString(), protocolVersion, exception);
break;
}
try {
originalPacket.encode(byteBuf, protocolVersion);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not encode packet {} for version {}: {}",
originalPacket, protocolVersion, throwable);
break;
}
// Make sure we don't unnecessarily fill the RAM
final int protocol = protocolVersion.getProtocol();
final int hash = byteBuf.hashCode();
final int hashed = hashes.getOrDefault(hash, -1);
if (hashed != -1) {
mappings.put(protocol, hashed);
} else {
hashes.put(hash, protocol);
mappings.put(protocol, protocol);
// Cache the raw bytes of the encoded packet
final byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
cachedBytes.put(protocol, bytes);
final Integer protocol = protocolVersion.getProtocol();
final Integer hash = byteBuf.hashCode();
final Integer hashed = hashedData.getOrDefault(hash, -1);
if (hashed != -1) {
mappings.put(protocol, hashed);
} else {
hashedData.put(hash, protocol);
mappings.put(protocol, protocol);
// Cache the raw bytes of the encoded packet
final byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
cachedBytes.put(protocol, bytes);
}
} finally {
// Make sure to release the buffer to avoid memory leaks
byteBuf.release();
}
// Make sure to release the buffer to avoid memory leaks
byteBuf.release();
}
this.originalPacket = originalPacket;
}
@Override
public void encode(final @NotNull ByteBuf byteBuf,
final @NotNull ProtocolVersion protocolVersion) throws Exception {
final int hash = mappings.get(protocolVersion.getProtocol());
final byte[] bytes = cachedBytes.get(hash);
if (bytes != null) {
byteBuf.writeBytes(bytes);
} else {
Sonar.get().getLogger().error("Could not find cached packet {} for version {}",
toString(), protocolVersion);
throw new IllegalStateException("Unable to find cached packet. Contact the developer!");
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
final Integer hash = mappings.get(protocolVersion.getProtocol());
if (hash == null) {
throw ProtocolUtil.DEBUG ? new EncoderException("Unable to find hash") : QuietDecoderException.INSTANCE;
}
final byte[] bytes = cachedBytes.get(hash);
if (bytes == null) {
throw ProtocolUtil.DEBUG ? new EncoderException("Unable to find cached packet") : QuietDecoderException.INSTANCE;
}
byteBuf.writeBytes(bytes);
}
@Override
@@ -22,15 +22,18 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.IntBinaryTag;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.event.impl.CaptchaGenerationStartEvent;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionType;
import xyz.jonesdev.sonar.common.fallback.protocol.entity.EntityType;
import xyz.jonesdev.sonar.common.fallback.protocol.item.ItemType;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.FinishConfigurationPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.RegistryDataPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
@@ -44,13 +47,8 @@ import java.util.UUID;
public class FallbackPreparer {
private final Random RANDOM = new Random();
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt(50);
public static final int VEHICLE_BOAT_ENTITY_ID = PLAYER_ENTITY_ID + 1 + RANDOM.nextInt(10);
public static final int VEHICLE_MINECART_ENTITY_ID = VEHICLE_BOAT_ENTITY_ID + 1 + RANDOM.nextInt(10);
public static final CompoundBinaryTag MAP_ITEM_NBT = CompoundBinaryTag.builder()
.put("map", IntBinaryTag.intBinaryTag(0)) // map type
.build();
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt();
public static final int VEHICLE_ENTITY_ID = RANDOM.nextInt();
public static final BlockType[] POSSIBLE_BLOCK_TYPES = BlockType.values();
public final FallbackPacket[] BLOCKS_PACKETS = new FallbackPacket[POSSIBLE_BLOCK_TYPES.length];
@@ -58,28 +56,48 @@ public class FallbackPreparer {
public final int BLOCKS_PER_ROW = 8; // 8 * 8 = 64 (vanilla protocol maximum)
public final int SPAWN_X_POSITION = 16 / 2; // middle of the chunk
public final int SPAWN_Z_POSITION = 16 / 2; // middle of the chunk
public final int DEFAULT_Y_COLLIDE_POSITION = 1 + RANDOM.nextInt(255); // 255 is the maximum Y position
public final int IN_AIR_Y_POSITION = 1000 + RANDOM.nextInt(338); // High altitude (randomized)
public final int IN_VOID_Y_POSITION = -(120 + RANDOM.nextInt(120)); // Low altitude (randomized)
public final int PLATFORM_Y_POSITION = 1 + RANDOM.nextInt(255); // 255 is the maximum Y position
public final int IN_AIR_Y_POSITION = 3000 + RANDOM.nextInt(500); // Random high altitude
public final int FIRST_TELEPORT_ID = RANDOM.nextInt();
public final int SECOND_TELEPORT_ID = RANDOM.nextInt();
public final int PRE_JOIN_KEEP_ALIVE_ID = RANDOM.nextInt();
private final int MAP_SLOT = RANDOM.nextInt(9);
public static final FallbackPacket CAPTCHA_SET_CONTAINER_SLOT = new SetContainerSlotPacket(
0, 36 + MAP_SLOT, 1, ItemType.FILLED_MAP, CompoundBinaryTag.builder()
.put("map", IntBinaryTag.intBinaryTag(0)) // map id
.build());
public final static FallbackPacket CAPTCHA_HELD_ITEM_SLOT = new SetHeldItemPacket(MAP_SLOT);
public final FallbackPacket DEFAULT_ABILITIES = new PlayerAbilitiesPacket(0x00, 0, 0);
public final FallbackPacket NO_MOVE_ABILITIES = new PlayerAbilitiesPacket(0x02, 0, 0);
public final FallbackPacket NO_MOVE_ABILITIES_BEDROCK = new PlayerAbilitiesPacket(0x06, 0, 0);
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION, 0, 90, 0, 0, false));
SPAWN_X_POSITION, 10000, SPAWN_Z_POSITION, 0, 90, 0, 0, false, false, true));
public final FallbackPacket EMPTY_CHUNK_DATA = new FallbackPacketSnapshot(new ChunkDataPacket(0, 0));
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfigurationPacket();
public final FallbackPacket REGISTRY_SYNC_LEGACY = new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null));
public final FallbackPacket PRE_JOIN_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(PRE_JOIN_KEEP_ALIVE_ID));
public final FallbackPacket[] REGISTRY_SYNC_1_20 = new FallbackPacket[] {
new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null))};
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20);
public final FallbackPacket[] REGISTRY_SYNC_1_21 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21);
public final FallbackPacket[] REGISTRY_SYNC_1_21_2 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_2);
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final static FallbackPacket CACHED_HELD_ITEM_SLOT = new SetHeldItemPacket(~4);
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new KeepAlivePacket(RANDOM.nextInt());
public final static FallbackPacket INVALID_HELD_ITEM_SLOT = new SetHeldItemPacket(-1);
public final FallbackPacket RANDOM_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(RANDOM.nextInt()));
public static final FallbackPacket SPAWN_BOAT_ENTITY = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_ENTITY_ID, EntityType.BOAT, SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, 0, 0, 0));
public static final FallbackPacket SPAWN_MINECART_ENTITY = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_ENTITY_ID, EntityType.MINECART, SPAWN_X_POSITION, IN_AIR_Y_POSITION - 16, SPAWN_Z_POSITION,
0, 0, 0, 0));
public static final FallbackPacket REMOVE_VEHICLE = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
public static final FallbackPacket SET_VEHICLE_PASSENGERS = new FallbackPacketSnapshot(
new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
public static FallbackPacket loginSuccess;
public FallbackPacket loginSuccess;
public FallbackPacket welcomeMessage;
public FallbackPacket enterCodeMessage;
public FallbackPacket incorrectCaptcha;
public static FallbackPacket joinGame;
@@ -95,13 +113,8 @@ public class FallbackPreparer {
public FallbackPacket protocolBlacklisted;
public FallbackPacket invalidUsername;
public FallbackPacket tooManyOnlinePerIP;
public FallbackPacket removeBoat;
public FallbackPacket teleportMinecart;
public FallbackPacket spawnBoatEntity;
public FallbackPacket spawnMinecartEntity;
public FallbackPacket setBoatPassengers;
public FallbackPacket setMinecartPassengers;
public FallbackPacket[] xpCountdown;
public FallbackPacket updateTime;
public static int maxMovementTick, dynamicSpawnYPosition, maxTotalPacketsSent;
@@ -111,16 +124,23 @@ public class FallbackPreparer {
Sonar.get().getLogger().info("Preloading all registered packets...");
FallbackPacketRegistry.values();
// Prepare LoginSuccess packet
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(UUID.randomUUID(),
Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username")));
// Prepare LoginSuccess packet with capped username to 16 characters
final UUID uuid = UUID.randomUUID();
String username = Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username");
if (username.length() > 16) {
username = username.substring(0, 16);
}
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(uuid, username, true));
// Prepare JoinGame packet
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGamemode().getId(), RANDOM.nextLong(),
0, 0, false, true, false,
false, false, false, true,
new String[]{"minecraft:overworld"}, "minecraft:overworld", "flat"));
Sonar.get().getConfig().getVerification().getGamemode().getId(),
-1, 0, 0,
RANDOM.nextInt(3), 1, 0, 0,
new String[]{"lol"}, "lol", "flat",
DimensionType.OVERWORLD, RANDOM.nextLong() & 1337,
false, true, false,
false, false, false, true));
// Prepare the gravity check
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
@@ -133,18 +153,18 @@ public class FallbackPreparer {
}
// Set the dynamic block and collide Y position based on the maximum fall distance
dynamicSpawnYPosition = DEFAULT_Y_COLLIDE_POSITION + (int) Math.ceil(fallDistance);
dynamicSpawnYPosition = PLATFORM_Y_POSITION + (int) Math.ceil(fallDistance);
defaultSpawnPosition = new FallbackPacketSnapshot(new SetDefaultSpawnPositionPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION));
spawnPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, 0, FIRST_TELEPORT_ID, 0, false));
0, 0, FIRST_TELEPORT_ID, 0, false, false, true));
fallStartPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, dynamicSpawnYPosition - IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, -90, SECOND_TELEPORT_ID, 1 << 1, false));
0, -90, SECOND_TELEPORT_ID, 1 << 1, false, false, true));
fallStartPositionLegacy = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION,
0, -90, 0, 0, false));
0, -90, 0, 0, false, false, true));
// Prepare collision platform positions
final int length = BLOCKS_PER_ROW / 2;
@@ -155,21 +175,21 @@ public class FallbackPreparer {
for (int x = 0; x < BLOCKS_PER_ROW; x++) {
for (int z = 0; z < BLOCKS_PER_ROW; z++) {
final BlockUpdate.BlockPosition position = new BlockUpdate.BlockPosition(
x + length, DEFAULT_Y_COLLIDE_POSITION, z + length, 0, 0);
x + length, PLATFORM_Y_POSITION, z + length, 0, 0);
blocks[index++] = new BlockUpdate(position, POSSIBLE_BLOCK_TYPES[i]);
}
}
BLOCKS_PACKETS[i] = new UpdateSectionBlocksPacket(0, 0, blocks);
BLOCKS_PACKETS[i] = new FallbackPacketSnapshot(new UpdateSectionBlocksPacket(0, 0, blocks));
}
// Prepare disconnect packets during login
blacklisted = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true));
invalidUsername = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getInvalidUsername(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getTooManyOnlinePerIp(), true));
blacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true));
invalidUsername = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getInvalidUsername(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getTooManyOnlinePerIp(), true));
// Prepare transfer packet
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("verification.transfer.enabled")) {
@@ -180,18 +200,23 @@ public class FallbackPreparer {
transferToOrigin = null;
}
// Prepare packets for the vehicle check
removeBoat = new RemoveEntitiesPacket(VEHICLE_BOAT_ENTITY_ID);
teleportMinecart = new TeleportEntityPacket(
VEHICLE_MINECART_ENTITY_ID, SPAWN_X_POSITION, IN_VOID_Y_POSITION, SPAWN_Z_POSITION, false);
spawnBoatEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_BOAT_ENTITY_ID,
EntityType.BOAT, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
spawnMinecartEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_MINECART_ENTITY_ID,
EntityType.MINECART, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
setBoatPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_BOAT_ENTITY_ID, PLAYER_ENTITY_ID));
setMinecartPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_MINECART_ENTITY_ID, PLAYER_ENTITY_ID));
// Prepare update time packet
final int timeOfDay = Sonar.get().getConfig().getVerification().getTimeOfDay();
if (timeOfDay != 1000) {
updateTime = new UpdateTimePacket(0L, timeOfDay, false);
} else {
updateTime = null;
}
// If the welcome message is empty, we don't need to send a message to the player
final String welcome = Sonar.get().getConfig().getMessagesConfig().getString("verification.welcome");
if (welcome.isEmpty()) {
welcomeMessage = null;
} else {
welcomeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(welcome,
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
}
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
@@ -242,4 +267,17 @@ public class FallbackPreparer {
+ Sonar.get().getConfig().getVerification().getMap().getMaxTries()
+ 150 /* some arbitrary leeway */;
}
public static FallbackPacket[] getRegistryPackets(final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
return REGISTRY_SYNC_1_21_2;
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21)) {
return REGISTRY_SYNC_1_21;
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
return REGISTRY_SYNC_1_20_5;
}
return REGISTRY_SYNC_1_20;
}
}
@@ -19,7 +19,6 @@ package xyz.jonesdev.sonar.common.fallback.protocol.block;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.function.Function;
@@ -27,6 +26,7 @@ import java.util.function.Function;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// TODO: load mappings from a separate file
@Getter
@SuppressWarnings("unused")
@RequiredArgsConstructor
public enum BlockType {
@@ -36,258 +36,274 @@ public enum BlockType {
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
//STONE(protocolVersion -> 1, 1),
ENCHANTMENT_TABLE(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 116;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 4612;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 4613;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 5116;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 5132;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 5136;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 5333;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 5719;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 7159;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 7385;
}
return 7389;
}, 0.75f),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 7389;
}
return 7619;
}, protocolVersion -> (double) 0.75f),
TRAPDOOR(protocolVersion -> {
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
if (protocolVersion.compareTo(MINECRAFT_1_7_6) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_7_6)) {
return 96;
}
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 167;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 6509;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 6510;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 7016;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 7552;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 7556;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 7802;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 8293;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 9937;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 10269;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 10273;
}
return 10414;
}, 0.1875),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 10414;
}
return 10749;
}, protocolVersion -> 0.1875),
END_PORTAL_FRAME(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 120;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 4633;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 4634;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 5137;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 5153;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 5157;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 5358;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 5744;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 7184;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 7410;
}
return 7414;
}, 0.8125),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 7414;
}
return 7644;
}, protocolVersion -> 0.8125),
DAYLIGHT_SENSOR(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 151;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 5651;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 5652;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 6158;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 6694;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 6698;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 6916;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 7327;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 8811;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 9063;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 9067;
}
return 9207;
}, 0.375),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 9207;
}
return 9462;
}, protocolVersion -> 0.375),
COBBLESTONE_WALL(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 139;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 5196;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 5197;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 5700;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 5660;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 5664;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 5866;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 6252;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 7692;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 7918;
}
return 7922;
}, 1.5),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 7922;
}
return 8152;
}, protocolVersion -> 1.5),
STONE_SLABS(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 44;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 7296;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 7297;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 7809;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 8345;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 8349;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 8595;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 9092;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 10748;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 11086;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 11090;
}
return 11231;
}, 0.5),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 11231;
}
return 11566;
}, protocolVersion -> 0.5),
WHITE_CARPET(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 171;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 6823;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 6824;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 7330;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 7866;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 7870;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 8116;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 8607;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 10251;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 10583;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 10587;
}
return 10728;
}, 0.0625);
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 10728;
}
return 11063;
}, protocolVersion -> protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 0 : 0.0625);
private final Function<ProtocolVersion, Integer> idFunction;
@Getter
private final double blockHeight;
public int getId(final @NotNull ProtocolVersion protocolVersion) {
return idFunction.apply(protocolVersion);
}
private final Function<ProtocolVersion, Integer> id;
private final Function<ProtocolVersion, Double> blockHeight;
}
@@ -19,12 +19,11 @@ package xyz.jonesdev.sonar.common.fallback.protocol.block;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
@Getter
public final class BlockUpdate {
private final @NotNull BlockPosition position;
private final BlockPosition position;
private final BlockType blockType;
private final int legacyBlockState, blockState;
@@ -39,7 +38,6 @@ public final class BlockUpdate {
}
@Getter
@ToString
@RequiredArgsConstructor
public static final class BlockPosition {
private final int x, y, z;
@@ -18,10 +18,8 @@
package xyz.jonesdev.sonar.common.fallback.protocol.dimension;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
@@ -38,9 +36,7 @@ public final class DimensionRegistry {
public final CompoundBinaryTag CODEC_1_19_4;
public final CompoundBinaryTag CODEC_1_20;
public final CompoundBinaryTag CODEC_1_21;
public final DimensionInfo DEFAULT_DIMENSION_1_16_2;
public final DimensionInfo DEFAULT_DIMENSION_1_18_2;
public final CompoundBinaryTag CODEC_1_21_2;
static {
CODEC_1_16 = getCodec("codec_1_16.nbt");
@@ -51,9 +47,7 @@ public final class DimensionRegistry {
CODEC_1_19_4 = getCodec("codec_1_19_4.nbt");
CODEC_1_20 = getCodec("codec_1_20.nbt");
CODEC_1_21 = getCodec("codec_1_21.nbt");
DEFAULT_DIMENSION_1_16_2 = getDimension(CODEC_1_16_2);
DEFAULT_DIMENSION_1_18_2 = getDimension(CODEC_1_18_2);
CODEC_1_21_2 = getCodec("codec_1_21_2.nbt");
}
private @NotNull CompoundBinaryTag getCodec(final @NotNull String fileName) {
@@ -64,10 +58,4 @@ public final class DimensionRegistry {
throw new IllegalStateException(throwable);
}
}
private static @NotNull DimensionInfo getDimension(final @NotNull CompoundBinaryTag tag) {
final ListBinaryTag dimensions = tag.getCompound("minecraft:dimension_type").getList("value");
final BinaryTag elementTag = ((CompoundBinaryTag) dimensions.get(0)).get("element");
return new DimensionInfo("minecraft:overworld", 0, (CompoundBinaryTag) elementTag);
}
}
@@ -19,14 +19,14 @@ package xyz.jonesdev.sonar.common.fallback.protocol.dimension;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import net.kyori.adventure.nbt.CompoundBinaryTag;
@Getter
@ToString
@RequiredArgsConstructor
public final class DimensionInfo {
private final String identifier;
private final int id;
private final CompoundBinaryTag tag;
public enum DimensionType {
OVERWORLD("minecraft:overworld", 0, 0),
THE_NETHER("minecraft:the_nether", -1, 1),
THE_END("minecraft:the_end", 1, 2);
private final String key;
private final int legacyId, id;
}
@@ -17,8 +17,8 @@
package xyz.jonesdev.sonar.common.fallback.protocol.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.function.Function;
@@ -26,66 +26,69 @@ import java.util.function.Function;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// TODO: load mappings from a separate file
@Getter
@RequiredArgsConstructor
public enum EntityType {
// Useful resources:
// - https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/
// - https://pokechu22.github.io/Burger/1.20.4.html
BOAT(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 1;
}
if (protocolVersion.compareTo(MINECRAFT_1_14_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_14_4)) {
return 5;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 6;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 7;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 8;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_3)) {
return 9;
}
return 10;
}),
MINECART(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 10;
}
if (protocolVersion.compareTo(MINECRAFT_1_14_4) <= 0) {
return 85; // 1.21.2 split the boat type in id registries.
}),
MINECART(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 10;
}
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_14_4)) {
return 41;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 42;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 45;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 50;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 53;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 54;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_2)) {
return 64;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_3)) {
return 65;
}
return 69;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 69;
}
return 82;
});
private final Function<ProtocolVersion, Integer> function;
public int getId(final @NotNull ProtocolVersion protocolVersion) {
return function.apply(protocolVersion);
}
private final Function<ProtocolVersion, Integer> id;
}
@@ -15,10 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
package xyz.jonesdev.sonar.common.fallback.protocol.item;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.function.Function;
@@ -26,6 +26,7 @@ import java.util.function.Function;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// TODO: load mappings from a separate file
@Getter
@RequiredArgsConstructor
public enum ItemType {
// Useful resources:
@@ -33,45 +34,45 @@ public enum ItemType {
// - https://pokechu22.github.io/Burger/1.20.4.html
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
FILLED_MAP(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 358;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 608;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 613;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 671;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 733;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 847;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 886;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 914;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 937;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_2)) {
return 941;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_3)) {
return 979;
}
return 982;
});
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 982;
}
return 1022;
}, protocolVersion -> protocolVersion.greaterThanOrEquals(MINECRAFT_1_21_2) ? 36 : 26);
private final Function<ProtocolVersion, Integer> idFunction;
public int getId(final @NotNull ProtocolVersion protocolVersion) {
return idFunction.apply(protocolVersion);
}
private final Function<ProtocolVersion, Integer> id;
private final Function<ProtocolVersion, Integer> components;
}
@@ -15,16 +15,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
package xyz.jonesdev.sonar.common.fallback.protocol.map;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.MapDataPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
@Getter
public final class MapCaptchaInfo {
private final String answer;
@@ -42,22 +41,21 @@ public final class MapCaptchaInfo {
}
this.legacy = new FallbackPacket[grid.length];
for (int i = 0; i < grid.length; i++) {
this.legacy[i] = new MapDataPacket(grid[i], i, 0);
this.legacy[i] = new MapDataPacket(grid[i], i, 0, 0, true);
}
// Prepare 1.8+ map data
this.modern = new MapDataPacket(buffer, 0, 0);
this.modern = new MapDataPacket(buffer, 0, 0, 0, true);
}
public void delayedWrite(final @NotNull FallbackUser user) {
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_8)) {
// 1.7.2-1.7.10 needs separate packets for each axis
for (final FallbackPacket legacyPacket : legacy) {
user.delayedWrite(legacyPacket);
for (final FallbackPacket packet : legacy) {
user.delayedWrite(packet);
}
return;
}
// Send modern packet
user.delayedWrite(modern);
}
}
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
package xyz.jonesdev.sonar.common.fallback.protocol.map;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +26,7 @@ import java.awt.image.Raster;
@UtilityClass
public class MapColorPalette {
// https://minecraft.wiki/w/Map_item_format#Full_color_tables
// TODO: load mappings from a separate file
public static final int[] COLOR_MAP = {
-1,
-1,
@@ -32,7 +32,7 @@ public final class FinishConfigurationPacket implements FallbackPacket {
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
// This packet is always empty; see https://wiki.vg/Protocol#Finish_Configuration
return 0;
}
@@ -26,12 +26,11 @@ import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketSnapshot;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.util.ArrayList;
import java.util.List;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@@ -42,19 +41,19 @@ public final class RegistryDataPacket implements FallbackPacket {
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_5) < 0) {
writeNamelessCompoundTag(byteBuf, tag);
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, tag);
} else if (type != null) {
writeString(byteBuf, type);
writeVarInt(byteBuf, bundles.size());
ProtocolUtil.writeString(byteBuf, type);
ProtocolUtil.writeVarInt(byteBuf, bundles.size());
for (final RegistryDataPacket.Bundle bundle : bundles) {
writeString(byteBuf, bundle.getName());
ProtocolUtil.writeString(byteBuf, bundle.getName());
// Write the bundle tag
final CompoundBinaryTag tag = bundle.getTag();
if (tag != null) {
byteBuf.writeBoolean(true);
writeNamelessCompoundTag(byteBuf, tag);
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, tag);
} else {
byteBuf.writeBoolean(false);
}
@@ -74,7 +73,8 @@ public final class RegistryDataPacket implements FallbackPacket {
final ArrayList<RegistryDataPacket.Bundle> bundles = new ArrayList<>();
for (final BinaryTag binaryTag : rootTag.getCompound(type).getList("value")) {
final CompoundBinaryTag tag = (CompoundBinaryTag) binaryTag;
bundles.add(new Bundle(tag.getString("name"), tag.getCompound("element")));
// CompoundBinaryTag#getCompound(String) will return an empty compound tag when not exist.
bundles.add(new Bundle(tag.getString("name"), tag.get("element") == null ? null : tag.getCompound("element")));
}
packets[index++] = new FallbackPacketSnapshot(new RegistryDataPacket(rootTag, type, bundles));
}
@@ -22,9 +22,7 @@ import lombok.Getter;
import lombok.ToString;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString(of = {"protocolVersionId", "hostname", "port"})
@@ -49,9 +47,9 @@ public final class HandshakePacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
protocolVersionId = readVarInt(byteBuf);
hostname = readString(byteBuf, MAXIMUM_HOSTNAME_LENGTH);
protocolVersionId = ProtocolUtil.readVarInt(byteBuf);
hostname = ProtocolUtil.readString(byteBuf, MAXIMUM_HOSTNAME_LENGTH);
port = byteBuf.readUnsignedShort();
intent = readVarInt(byteBuf);
intent = ProtocolUtil.readVarInt(byteBuf);
}
}
@@ -33,7 +33,7 @@ public final class LoginAcknowledgedPacket implements FallbackPacket {
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
// This packet is always empty; see https://wiki.vg/Protocol#Login_Acknowledged
return 0;
}
@@ -18,6 +18,7 @@
package xyz.jonesdev.sonar.common.fallback.protocol.packets.login;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -26,11 +27,11 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.util.UUID;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@@ -46,31 +47,32 @@ public final class LoginStartPacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
username = readString(byteBuf, 16);
username = ProtocolUtil.readString(byteBuf, 16);
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
if (byteBuf.readBoolean()) {
readPlayerKey(byteBuf);
final long expiry = byteBuf.readLong();
final long now = System.currentTimeMillis();
if (expiry < now) {
throw ProtocolUtil.DEBUG ? new DecoderException("Bad expiry: " + expiry) : QuietDecoderException.INSTANCE;
}
final byte[] key = ProtocolUtil.readByteArray(byteBuf);
if (key.length == 0) {
throw ProtocolUtil.DEBUG ? new DecoderException("Empty key") : QuietDecoderException.INSTANCE;
}
final byte[] signature = ProtocolUtil.readByteArray(byteBuf, 4096);
if (signature.length == 0) {
throw ProtocolUtil.DEBUG ? new DecoderException("Empty signature") : QuietDecoderException.INSTANCE;
}
}
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
uuid = readUUID(byteBuf);
return;
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (byteBuf.readBoolean()) {
uuid = readUUID(byteBuf);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_1)) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_2) || byteBuf.readBoolean()) {
uuid = ProtocolUtil.readUUID(byteBuf);
}
}
}
}
private static void readPlayerKey(final @NotNull ByteBuf byteBuf) {
final long expiry = byteBuf.readLong();
final byte[] key = readByteArray(byteBuf);
final byte[] signature = readByteArray(byteBuf, 4096);
}
}
@@ -26,12 +26,10 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.FastUuidSansHyphens;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@@ -39,30 +37,29 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
public final class LoginSuccessPacket implements FallbackPacket {
private UUID uuid;
private String username;
private boolean strictErrorHandling;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
writeUUID(byteBuf, uuid);
} else if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0) {
writeUUIDIntArray(byteBuf, uuid);
} else if (protocolVersion.compareTo(MINECRAFT_1_7_6) >= 0) {
writeString(byteBuf, uuid.toString());
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16)) {
ProtocolUtil.writeUUID(byteBuf, uuid);
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_7_6)) {
ProtocolUtil.writeString(byteBuf, uuid.toString());
} else {
writeString(byteBuf, FastUuidSansHyphens.toString(uuid));
ProtocolUtil.writeString(byteBuf, FastUuidSansHyphens.toString(uuid));
}
// Cap the length of the username to 16 characters
if (username.length() > 16) {
username = username.substring(0, 16);
}
writeString(byteBuf, username);
ProtocolUtil.writeString(byteBuf, username);
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
writeVarInt(byteBuf, 0); // properties
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
// We don't need to send any properties to the client
ProtocolUtil.writeVarInt(byteBuf, 0);
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) >= 0) {
byteBuf.writeBoolean(false); // should authenticate
if (protocolVersion.equals(ProtocolVersion.MINECRAFT_1_20_5)
|| protocolVersion.equals(ProtocolVersion.MINECRAFT_1_21)) {
// Whether the client should disconnect on its own if it receives invalid data from the server
byteBuf.writeBoolean(strictErrorHandling);
}
}
@@ -22,9 +22,7 @@ import lombok.*;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@Setter
@@ -45,12 +43,12 @@ public final class AnimationPacket implements FallbackPacket {
@Override
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
entityId = byteBuf.readInt();
type = LegacyAnimationType.getById(byteBuf.readByte());
} else if (protocolVersion.compareTo(MINECRAFT_1_8) > 0) {
} else if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_8)) {
// Only 1.9+ clients have an offhand
hand = readVarInt(byteBuf);
hand = ProtocolUtil.readVarInt(byteBuf);
}
}
@@ -27,9 +27,7 @@ import net.kyori.adventure.nbt.LongArrayBinaryTag;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -38,70 +36,46 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
public final class ChunkDataPacket implements FallbackPacket {
private int sectionX, sectionZ;
private static final byte[] SECTION_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 1, 0};
private static final byte[] LIGHT_BYTES = new byte[]{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, -1, -1, 0, 0};
private static final byte[] LEGACY_FILLER_BYTES_17 = new byte[2];
private static final byte[] LEGACY_FILLER_BYTES = new byte[256];
private static final byte[] MODERN_FILLER_BYTES = new byte[256 * 4];
// Prepare nbt for 1.18 and pre-1.18
private static final CompoundBinaryTag MODERN_TAG, LEGACY_TAG;
static {
MODERN_TAG = prepareNBT(false);
LEGACY_TAG = prepareNBT(true);
}
private static @NotNull CompoundBinaryTag prepareNBT(final boolean legacy) {
final long[] arrayData = new long[legacy ? 36 : 37];
final LongArrayBinaryTag longArray = LongArrayBinaryTag.longArrayBinaryTag(arrayData);
final CompoundBinaryTag motion = CompoundBinaryTag.builder()
.put("MOTION_BLOCKING", longArray)
.build();
return CompoundBinaryTag.builder()
.put("root", motion)
.build();
}
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeInt(sectionX);
byteBuf.writeInt(sectionZ);
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_17_1) <= 0) {
writeVarInt(byteBuf, 0); // mask
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17)) {
if (protocolVersion.lessThanOrEquals(ProtocolVersion.MINECRAFT_1_17_1)) {
ProtocolUtil.writeVarInt(byteBuf, 0); // mask
}
} else {
byteBuf.writeBoolean(true); // full chunk
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
&& protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
if (protocolVersion.inBetween(ProtocolVersion.MINECRAFT_1_16, ProtocolVersion.MINECRAFT_1_16_1)) {
byteBuf.writeBoolean(true); // ignore old data
}
if (protocolVersion.compareTo(MINECRAFT_1_8) > 0) {
writeVarInt(byteBuf, 0);
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
} else {
byteBuf.writeShort(1); // fix void chunk
}
}
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0) {
writeNamelessCompoundTag(byteBuf, MODERN_TAG);
} else {
writeCompoundTag(byteBuf, protocolVersion.compareTo(MINECRAFT_1_18) < 0 ? LEGACY_TAG : MODERN_TAG);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_14)) {
final long[] motionBlockingData = new long[protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_18) ? 36 : 37];
final CompoundBinaryTag motionBlockingTag = CompoundBinaryTag.builder()
.put("MOTION_BLOCKING", LongArrayBinaryTag.longArrayBinaryTag(motionBlockingData))
.build();
final CompoundBinaryTag rootTag = CompoundBinaryTag.builder()
.put("root", motionBlockingTag)
.build();
if (protocolVersion.compareTo(MINECRAFT_1_15) >= 0 && protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
writeVarInt(byteBuf, 1024);
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, rootTag);
if (protocolVersion.inBetween(ProtocolVersion.MINECRAFT_1_15, ProtocolVersion.MINECRAFT_1_17_1)) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16_2)) {
ProtocolUtil.writeVarInt(byteBuf, 1024);
for (int i = 0; i < 1024; i++) {
writeVarInt(byteBuf, 1);
ProtocolUtil.writeVarInt(byteBuf, 1);
}
} else {
for (int i = 0; i < 1024; i++) {
@@ -111,36 +85,40 @@ public final class ChunkDataPacket implements FallbackPacket {
}
}
if (protocolVersion.compareTo(MINECRAFT_1_13) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
writeByteArray(byteBuf, LEGACY_FILLER_BYTES); // 1.8 - 1.12.2
} else {
byteBuf.writeInt(0); // compressed size
byteBuf.writeBytes(LEGACY_FILLER_BYTES_17); // 1.7
}
} else if (protocolVersion.compareTo(MINECRAFT_1_15) < 0) {
writeByteArray(byteBuf, MODERN_FILLER_BYTES); // 1.13 - 1.14.4
} else if (protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
writeVarInt(byteBuf, 0); // 1.15 - 1.17.1
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeInt(0);
byteBuf.writeBytes(new byte[2]);
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_15)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_18)) {
ProtocolUtil.writeByteArray(byteBuf, new byte[256 * 4]);
} else {
writeVarInt(byteBuf, SECTION_BYTES.length * 16);
final byte[] sectionData = new byte[]{0, 0, 0, 0, 0, 0, 1, 0};
int count = protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2) ? 24 : 16;
ProtocolUtil.writeVarInt(byteBuf, sectionData.length * count);
for (int i = 0; i < 16; i++) {
byteBuf.writeBytes(SECTION_BYTES);
for (int i = 0; i < count; i++) {
byteBuf.writeBytes(sectionData);
}
}
if (protocolVersion.compareTo(MINECRAFT_1_9_4) >= 0) {
writeVarInt(byteBuf, 0);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9_4)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
}
if (protocolVersion.compareTo(MINECRAFT_1_18) >= 0) {
byteBuf.ensureWritable(LIGHT_BYTES.length);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
for (int i = 0; i < 6; i++) {
ProtocolUtil.writeVarInt(byteBuf, 0);
}
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_18)) {
final byte[] lightData = new byte[]{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, -1, -1, 0, 0};
if (protocolVersion.compareTo(MINECRAFT_1_20) >= 0) {
byteBuf.writeBytes(LIGHT_BYTES, 1, LIGHT_BYTES.length - 1);
byteBuf.ensureWritable(lightData.length);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20)) {
byteBuf.writeBytes(lightData, 1, lightData.length - 1);
} else {
byteBuf.writeBytes(LIGHT_BYTES);
byteBuf.writeBytes(lightData);
}
}
}
@@ -25,10 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -44,6 +41,7 @@ public final class ClientInformationPacket implements FallbackPacket {
private int mainHand;
private boolean chatFilteringEnabled; // Added in 1.17
private boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode
private int particleStatus;
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
@@ -52,37 +50,41 @@ public final class ClientInformationPacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
locale = readString(byteBuf, 16);
locale = ProtocolUtil.readString(byteBuf, 16);
viewDistance = byteBuf.readByte();
chatVisibility = readVarInt(byteBuf);
chatVisibility = ProtocolUtil.readVarInt(byteBuf);
chatColors = byteBuf.readBoolean();
if (protocolVersion.compareTo(MINECRAFT_1_7_6) <= 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
difficulty = byteBuf.readByte();
}
skinParts = byteBuf.readUnsignedByte();
if (protocolVersion.compareTo(MINECRAFT_1_9) >= 0) {
mainHand = readVarInt(byteBuf);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)) {
mainHand = ProtocolUtil.readVarInt(byteBuf);
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17)) {
chatFilteringEnabled = byteBuf.readBoolean();
if (protocolVersion.compareTo(MINECRAFT_1_18) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_18)) {
clientListingAllowed = byteBuf.readBoolean();
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
particleStatus = ProtocolUtil.readVarInt(byteBuf);
}
}
}
}
}
@Override
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMinLength(final ProtocolVersion protocolVersion) {
return 2;
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
return 0xff + 1; // 256 as a hard-limit
}
}
@@ -24,8 +24,7 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -41,6 +40,6 @@ public final class ConfirmTeleportationPacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
teleportId = readVarInt(byteBuf);
teleportId = ProtocolUtil.readVarInt(byteBuf);
}
}
@@ -21,7 +21,6 @@ import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
@@ -29,24 +28,25 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.ComponentHolder;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class DisconnectPacket implements FallbackPacket {
private @NotNull ComponentHolder componentHolder;
private boolean duringLogin;
public DisconnectPacket(final @NotNull Component component, final boolean duringLogin) {
this.componentHolder = new ComponentHolder(component);
this.duringLogin = duringLogin;
}
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
componentHolder.write(byteBuf, duringLogin ? ProtocolVersion.MINECRAFT_1_20_2 : protocolVersion);
// Force json serialization during login phase
componentHolder.write(byteBuf, protocolVersion, duringLogin);
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
public static @NotNull DisconnectPacket create(final @NotNull Component component, final boolean duringLogin) {
return new DisconnectPacket(new ComponentHolder(component), duringLogin);
}
}
@@ -24,8 +24,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@Setter
@@ -37,7 +36,7 @@ public final class EntityAnimationPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
writeVarInt(byteBuf, entityId);
ProtocolUtil.writeVarInt(byteBuf, entityId);
byteBuf.writeByte(type.ordinal());
}
@@ -21,238 +21,153 @@ import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionType;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
import java.util.Objects;
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/packets/play/PacketJoinGame.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public final class JoinGamePacket implements FallbackPacket {
private int entityId;
private int gamemode;
private long partialHashedSeed;
private int previousGamemode;
private int viewDistance;
private int simulationDistance;
private boolean isHardcore;
private int difficulty;
private int maxPlayers;
private int portalCooldown;
private int seaLevel;
private String[] levelNames;
private String levelName;
private String levelType;
private DimensionType dimension;
private long partialHashedSeed;
private boolean hardcore;
private boolean reducedDebugInfo;
private boolean showRespawnScreen;
private boolean isDebug;
private boolean isFlat;
private boolean debug;
private boolean flat;
private boolean limitedCrafting;
private boolean secureProfile;
private String[] levelNames;
private String worldType;
private String levelType;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeInt(entityId);
if (protocolVersion.inBetween(MINECRAFT_1_7_2, MINECRAFT_1_7_6)) {
byteBuf.writeByte(gamemode == 3 ? 1 : gamemode);
byteBuf.writeByte(DEFAULT_DIMENSION_1_16_2.getId());
byteBuf.writeByte(0); // difficulty
byteBuf.writeByte(0); // max players
writeString(byteBuf, levelType);
return;
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16_2)) {
byteBuf.writeBoolean(hardcore);
}
if (protocolVersion.inBetween(MINECRAFT_1_8, MINECRAFT_1_9)) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
byteBuf.writeByte(gamemode);
byteBuf.writeByte(DEFAULT_DIMENSION_1_16_2.getId());
byteBuf.writeByte(0); // difficulty
byteBuf.writeByte(0); // max players
writeString(byteBuf, levelType);
byteBuf.writeBoolean(reducedDebugInfo);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_9_1, MINECRAFT_1_13_2)) {
byteBuf.writeByte(gamemode);
byteBuf.writeInt(DEFAULT_DIMENSION_1_16_2.getId());
byteBuf.writeByte(0); // difficulty
byteBuf.writeByte(0); // max players
writeString(byteBuf, levelType);
byteBuf.writeBoolean(reducedDebugInfo);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_14, MINECRAFT_1_14_4)) {
byteBuf.writeByte(gamemode);
byteBuf.writeInt(DEFAULT_DIMENSION_1_16_2.getId());
byteBuf.writeByte(0); // max players
writeString(byteBuf, levelType);
writeVarInt(byteBuf, viewDistance);
byteBuf.writeBoolean(reducedDebugInfo);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_15, MINECRAFT_1_15_2)) {
byteBuf.writeByte(gamemode);
byteBuf.writeInt(DEFAULT_DIMENSION_1_16_2.getId());
byteBuf.writeLong(partialHashedSeed);
byteBuf.writeByte(0); // max players
writeString(byteBuf, levelType);
writeVarInt(byteBuf, viewDistance);
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_16, MINECRAFT_1_16_1)) {
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
writeStringArray(byteBuf, levelNames);
writeCompoundTag(byteBuf, CODEC_1_16);
writeString(byteBuf, DEFAULT_DIMENSION_1_16_2.getIdentifier());
writeString(byteBuf, levelType);
byteBuf.writeLong(partialHashedSeed);
byteBuf.writeByte(0); // max players
writeVarInt(byteBuf, viewDistance);
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
byteBuf.writeBoolean(isDebug);
byteBuf.writeBoolean(isFlat);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_16_2, MINECRAFT_1_17_1)) {
byteBuf.writeBoolean(isHardcore);
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
writeStringArray(byteBuf, levelNames);
writeCompoundTag(byteBuf, CODEC_1_16_2);
writeCompoundTag(byteBuf, DEFAULT_DIMENSION_1_16_2.getTag());
writeString(byteBuf, levelType);
byteBuf.writeLong(partialHashedSeed);
writeVarInt(byteBuf, 0); // max players
writeVarInt(byteBuf, viewDistance);
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
byteBuf.writeBoolean(isDebug);
byteBuf.writeBoolean(isFlat);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_18, MINECRAFT_1_18_2)) {
byteBuf.writeBoolean(isHardcore);
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
writeStringArray(byteBuf, levelNames);
if (protocolVersion.compareTo(MINECRAFT_1_18_2) >= 0) {
writeCompoundTag(byteBuf, CODEC_1_18_2);
writeCompoundTag(byteBuf, DEFAULT_DIMENSION_1_18_2.getTag());
} else {
writeCompoundTag(byteBuf, CODEC_1_16_2);
writeCompoundTag(byteBuf, DEFAULT_DIMENSION_1_16_2.getTag());
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16)) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
byteBuf.writeByte(previousGamemode);
}
writeString(byteBuf, levelType);
byteBuf.writeLong(partialHashedSeed);
writeVarInt(byteBuf, 0); // max players
writeVarInt(byteBuf, viewDistance);
writeVarInt(byteBuf, simulationDistance);
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
byteBuf.writeBoolean(isDebug);
byteBuf.writeBoolean(isFlat);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_19, MINECRAFT_1_19_4)) {
byteBuf.writeBoolean(isHardcore);
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
writeStringArray(byteBuf, levelNames);
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_19_4) >= 0) {
writeCompoundTag(byteBuf, CODEC_1_19_4);
} else {
writeCompoundTag(byteBuf, CODEC_1_19_1);
}
} else {
writeCompoundTag(byteBuf, CODEC_1_19);
ProtocolUtil.writeStringArray(byteBuf, levelNames);
final CompoundBinaryTag codec = ProtocolUtil.getCodec(protocolVersion);
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, codec);
}
writeString(byteBuf, worldType);
writeString(byteBuf, levelType);
byteBuf.writeLong(partialHashedSeed);
writeVarInt(byteBuf, 0); // max players
writeVarInt(byteBuf, viewDistance);
writeVarInt(byteBuf, simulationDistance);
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
byteBuf.writeBoolean(isDebug);
byteBuf.writeBoolean(isFlat);
byteBuf.writeBoolean(false); // no last death location
return;
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16_2)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
final ListBinaryTag dimensions = codec.getCompound("minecraft:dimension_type").getList("value");
final BinaryTag elementTag = ((CompoundBinaryTag) dimensions.get(0)).get("element");
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, Objects.requireNonNull(elementTag));
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
ProtocolUtil.writeString(byteBuf, dimension.getKey());
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
ProtocolUtil.writeString(byteBuf, levelName);
}
} else if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_9)) {
byteBuf.writeInt(dimension.getLegacyId());
} else {
byteBuf.writeByte(dimension.getLegacyId());
}
if (protocolVersion.equals(MINECRAFT_1_20)) {
byteBuf.writeBoolean(isHardcore);
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
writeStringArray(byteBuf, levelNames);
writeCompoundTag(byteBuf, CODEC_1_20);
writeString(byteBuf, worldType);
writeString(byteBuf, levelType);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_15)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
byteBuf.writeLong(partialHashedSeed);
writeVarInt(byteBuf, 0); // max players
writeVarInt(byteBuf, viewDistance);
writeVarInt(byteBuf, simulationDistance);
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
byteBuf.writeBoolean(isDebug);
byteBuf.writeBoolean(isFlat);
byteBuf.writeBoolean(false); // no last death location
writeVarInt(byteBuf, 0);
return;
}
if (protocolVersion.inBetween(MINECRAFT_1_20_2, MINECRAFT_1_20_3)) {
byteBuf.writeBoolean(isHardcore);
writeStringArray(byteBuf, levelNames);
writeVarInt(byteBuf, 0); // max players
writeVarInt(byteBuf, viewDistance);
writeVarInt(byteBuf, simulationDistance);
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_14)) {
byteBuf.writeByte(difficulty);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16_2)) {
ProtocolUtil.writeVarInt(byteBuf, maxPlayers);
} else {
byteBuf.writeByte(maxPlayers);
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_16)) {
ProtocolUtil.writeString(byteBuf, levelType);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_14)) {
ProtocolUtil.writeVarInt(byteBuf, viewDistance);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_18)) {
ProtocolUtil.writeVarInt(byteBuf, simulationDistance);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeBoolean(reducedDebugInfo);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_15)) {
byteBuf.writeBoolean(showRespawnScreen);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_2)) {
byteBuf.writeBoolean(limitedCrafting);
writeString(byteBuf, worldType);
writeString(byteBuf, levelType);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtil.writeVarInt(byteBuf, dimension.getId());
} else {
ProtocolUtil.writeString(byteBuf, dimension.getKey());
}
ProtocolUtil.writeString(byteBuf, levelName);
byteBuf.writeLong(partialHashedSeed);
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
byteBuf.writeBoolean(isDebug);
byteBuf.writeBoolean(isFlat);
byteBuf.writeBoolean(false); // no last death location
writeVarInt(byteBuf, 0);
return;
byteBuf.writeByte(previousGamemode);
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) >= 0) {
byteBuf.writeBoolean(isHardcore);
writeStringArray(byteBuf, levelNames);
writeVarInt(byteBuf, 0); // max players
writeVarInt(byteBuf, viewDistance);
writeVarInt(byteBuf, simulationDistance);
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
byteBuf.writeBoolean(limitedCrafting);
// It depends on the dimension data we send in RegistryData. If we only send one dimension. It should be 0.
writeVarInt(byteBuf, 0);
writeString(byteBuf, levelType);
byteBuf.writeLong(partialHashedSeed);
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
byteBuf.writeBoolean(isDebug);
byteBuf.writeBoolean(isFlat);
byteBuf.writeBoolean(false); // no last death location
writeVarInt(byteBuf, 0); // pearl cooldown
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16)) {
byteBuf.writeBoolean(debug);
byteBuf.writeBoolean(flat);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
byteBuf.writeBoolean(false); // last death location
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20)) {
ProtocolUtil.writeVarInt(byteBuf, portalCooldown);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtil.writeVarInt(byteBuf, seaLevel);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
byteBuf.writeBoolean(secureProfile);
}
}
@@ -25,11 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_12_2;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -40,10 +36,10 @@ public final class KeepAlivePacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_12_2)) {
byteBuf.writeLong(id);
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
writeVarInt(byteBuf, (int) id);
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtil.writeVarInt(byteBuf, (int) id);
} else {
byteBuf.writeInt((int) id);
}
@@ -51,10 +47,10 @@ public final class KeepAlivePacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_12_2)) {
id = byteBuf.readLong();
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
id = readVarInt(byteBuf);
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
id = ProtocolUtil.readVarInt(byteBuf);
} else {
id = byteBuf.readInt();
}
@@ -24,8 +24,7 @@ import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@NoArgsConstructor
@@ -33,43 +32,46 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
public final class MapDataPacket implements FallbackPacket {
private byte[] buffer;
private int x, y;
private int scaling;
private boolean locked;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
writeVarInt(byteBuf, 0);
ProtocolUtil.writeVarInt(byteBuf, 0); // item damage
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeShort(buffer.length + 3);
byteBuf.writeByte(0); // scaling
byteBuf.writeByte(0);
byteBuf.writeByte(x);
byteBuf.writeByte(y);
byteBuf.writeBytes(buffer);
} else {
byteBuf.writeByte(0); // scaling
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0
&& protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) < 0) {
byteBuf.writeBoolean(false);
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
byteBuf.writeBoolean(false);
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) {
byteBuf.writeBoolean(false);
} else {
writeVarInt(byteBuf, 0);
}
byteBuf.writeByte(128); // rows
byteBuf.writeByte(128); // columns
byteBuf.writeByte(x);
byteBuf.writeByte(y);
writeVarInt(byteBuf, buffer.length);
byteBuf.writeBytes(buffer);
return;
}
byteBuf.writeByte(scaling);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_17)) {
byteBuf.writeBoolean(false); // no icon
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_14)) {
byteBuf.writeBoolean(locked);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17)) {
byteBuf.writeBoolean(false); // no icon
} else {
ProtocolUtil.writeVarInt(byteBuf, 0); // no icon
}
byteBuf.writeByte(128); // rows
byteBuf.writeByte(128); // columns
byteBuf.writeByte(x);
byteBuf.writeByte(y);
ProtocolUtil.writeVarInt(byteBuf, buffer.length);
byteBuf.writeBytes(buffer);
}
@Override
@@ -26,33 +26,38 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class PlayerInputPacket implements FallbackPacket {
private float sideways, forward;
private boolean jump, unmount;
private boolean jump, sneak;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
final byte mask = byteBuf.readByte();
jump = (mask & 16) != 0;
sneak = (mask & 32) != 0;
return;
}
sideways = byteBuf.readFloat();
forward = byteBuf.readFloat();
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
jump = byteBuf.readBoolean();
unmount = byteBuf.readBoolean();
sneak = byteBuf.readBoolean();
} else {
final byte flags = byteBuf.readByte();
jump = (flags & 0x01) != 0;
unmount = (flags & 0x02) != 0;
sneak = (flags & 0x02) != 0;
}
}
}
@@ -23,12 +23,9 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readExtendedForgeShort;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
@Getter
@ToString
public final class PluginMessagePacket implements FallbackPacket {
@@ -44,16 +41,16 @@ public final class PluginMessagePacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
channel = readString(byteBuf, 48);
channel = ProtocolUtil.readString(byteBuf, 48);
final int length;
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
length = byteBuf.readableBytes();
if (length > Short.MAX_VALUE) {
throw QuietDecoderException.INSTANCE;
}
} else {
length = readExtendedForgeShort(byteBuf);
length = ProtocolUtil.readExtendedForgeShort(byteBuf);
if (length > FORGE_MAX_ARRAY_LENGTH) {
throw QuietDecoderException.INSTANCE;
}
@@ -64,12 +61,12 @@ public final class PluginMessagePacket implements FallbackPacket {
}
@Override
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMinLength(final ProtocolVersion protocolVersion) {
return 4;
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
return 0xFFF; // strict size limit
}
}
@@ -25,8 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -38,14 +37,14 @@ public final class RemoveEntitiesPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
// No idea why Mojang decided that this is a good idea, but whatever
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) != 0) {
writeVarInt(byteBuf, 1); // size
if (!protocolVersion.equals(ProtocolVersion.MINECRAFT_1_17)) {
ProtocolUtil.writeVarInt(byteBuf, 1); // size
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeInt(entityId);
} else {
writeVarInt(byteBuf, entityId);
ProtocolUtil.writeVarInt(byteBuf, entityId);
}
}
@@ -26,10 +26,8 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.ItemType;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
import xyz.jonesdev.sonar.common.fallback.protocol.item.ItemType;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -42,61 +40,58 @@ public final class SetContainerSlotPacket implements FallbackPacket {
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeByte(windowId);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtil.writeVarInt(byteBuf, windowId);
} else {
byteBuf.writeByte(windowId);
}
if (protocolVersion.compareTo(MINECRAFT_1_17_1) >= 0) {
writeVarInt(byteBuf, 0);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17_1)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
}
byteBuf.writeShort(slot);
if (protocolVersion.inBetween(MINECRAFT_1_13_2, MINECRAFT_1_20_3)) {
if (protocolVersion.inBetween(ProtocolVersion.MINECRAFT_1_13_2, ProtocolVersion.MINECRAFT_1_20_3)) {
byteBuf.writeBoolean(true);
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) >= 0) {
writeVarInt(byteBuf, count);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtil.writeVarInt(byteBuf, count);
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) < 0) {
byteBuf.writeShort(itemType.getId(protocolVersion));
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13_2)) {
byteBuf.writeShort(itemType.getId().apply(protocolVersion));
} else {
writeVarInt(byteBuf, itemType.getId(protocolVersion));
ProtocolUtil.writeVarInt(byteBuf, itemType.getId().apply(protocolVersion));
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
byteBuf.writeByte(count);
}
if (protocolVersion.compareTo(MINECRAFT_1_13) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13)) {
byteBuf.writeShort(0); // data
}
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_17)) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeShort(-1);
} else {
byteBuf.writeByte(0);
}
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, compoundBinaryTag);
} else {
if (protocolVersion.compareTo(MINECRAFT_1_20_2) < 0) {
writeCompoundTag(byteBuf, compoundBinaryTag);
} else if (protocolVersion.compareTo(MINECRAFT_1_20_5) < 0) {
writeNamelessCompoundTag(byteBuf, compoundBinaryTag);
} else { // 1.20.5+
// TODO: find a way to improve this
// component
writeVarInt(byteBuf, 1); // component count to add
writeVarInt(byteBuf, 0); // component count to remove
// single VarInt component
writeVarInt(byteBuf, 26); // map component
writeVarInt(byteBuf, 0); // map id
}
ProtocolUtil.writeVarInt(byteBuf, 1); // component count to add
ProtocolUtil.writeVarInt(byteBuf, 0); // component count to remove
ProtocolUtil.writeVarInt(byteBuf, itemType.getComponents().apply(protocolVersion));
ProtocolUtil.writeVarInt(byteBuf, 0); // data
}
}
@Override
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
}
@@ -26,9 +26,6 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_14;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_17;
@Getter
@ToString
@NoArgsConstructor
@@ -38,18 +35,18 @@ public final class SetDefaultSpawnPositionPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeInt(x);
byteBuf.writeInt(y);
byteBuf.writeInt(z);
} else {
final long encoded = protocolVersion.compareTo(MINECRAFT_1_14) < 0
final long encoded = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_14)
? ((x & 0x3FFFFFFL) << 38) | ((y & 0xFFFL) << 26) | (z & 0x3FFFFFFL)
: ((x & 0x3FFFFFFL) << 38) | ((y & 0x3FFFFFFL) << 12) | (z & 0xFFFL);
byteBuf.writeLong(encoded);
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17)) {
byteBuf.writeFloat(0f);
}
}
@@ -25,9 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -41,13 +39,15 @@ public final class SetExperiencePacket implements FallbackPacket {
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeFloat(experienceBar);
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeShort(level);
byteBuf.writeShort(totalExperience);
} else {
writeVarInt(byteBuf, level);
writeVarInt(byteBuf, totalExperience);
return;
}
ProtocolUtil.writeVarInt(byteBuf, level);
ProtocolUtil.writeVarInt(byteBuf, totalExperience);
}
@Override
@@ -25,9 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_9;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -40,16 +38,16 @@ public final class SetPassengersPacket implements FallbackPacket {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
// You can find this in the EntityAttach packet,
// which was later replaced by SetPassengers in 1.9+
if (protocolVersion.compareTo(MINECRAFT_1_9) < 0) {
if (protocolVersion.lessThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeInt(passengerId);
byteBuf.writeInt(entityId);
byteBuf.writeByte(0); // leash
return;
}
writeVarInt(byteBuf, entityId);
writeVarInt(byteBuf, 1); // passenger count
writeVarInt(byteBuf, passengerId);
ProtocolUtil.writeVarInt(byteBuf, entityId);
ProtocolUtil.writeVarInt(byteBuf, 1); // passenger count
ProtocolUtil.writeVarInt(byteBuf, passengerId);
}
@Override
@@ -31,7 +31,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@NoArgsConstructor
@AllArgsConstructor
public final class SetPlayerOnGround implements FallbackPacket {
private boolean onGround;
private boolean onGround, horizontalCollision;
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
@@ -40,6 +40,12 @@ public final class SetPlayerOnGround implements FallbackPacket {
@Override
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
onGround = byteBuf.readBoolean();
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
final short flag = byteBuf.readUnsignedByte();
onGround = (flag & 1) != 0;
horizontalCollision = (flag & 2) != 0;
} else {
onGround = byteBuf.readBoolean();
}
}
}
@@ -26,15 +26,13 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class SetPlayerPositionPacket implements FallbackPacket {
private double x, y, z;
private boolean onGround;
private boolean onGround, horizontalCollision;
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
@@ -45,21 +43,27 @@ public final class SetPlayerPositionPacket implements FallbackPacket {
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
x = byteBuf.readDouble();
y = byteBuf.readDouble();
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
// 1.7.2-1.7.10 send the minimum bounding box Y coordinate
byteBuf.readDouble();
}
z = byteBuf.readDouble();
onGround = byteBuf.readBoolean();
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_21_2)) {
short flag = byteBuf.readUnsignedByte();
onGround = (flag & 1) != 0;
horizontalCollision = (flag & 2) != 0;
} else {
onGround = byteBuf.readBoolean();
}
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
return protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 33 : 25;
public int expectedMaxLength(final @NotNull ProtocolVersion protocolVersion) {
return protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8) ? 33 : 25;
}
@Override
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMinLength(final ProtocolVersion protocolVersion) {
return 25;
}
}
@@ -25,9 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -37,24 +35,41 @@ public final class SetPlayerPositionRotationPacket implements FallbackPacket {
private double x, y, z;
private float yaw, pitch;
private int teleportId, relativeMask;
private boolean onGround;
private boolean onGround, horizontalCollision;
private boolean dismountVehicle;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
final boolean v1_21_2 = protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2);
if (v1_21_2) {
ProtocolUtil.writeVarInt(byteBuf, teleportId);
}
byteBuf.writeDouble(x);
// Account for the minimum Y bounding box issue on 1.7.2-1.7.10
byteBuf.writeDouble(protocolVersion.compareTo(MINECRAFT_1_8) >= 0 ? y : y + 1.62f);
byteBuf.writeDouble(protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8) ? y : y + 1.62f);
byteBuf.writeDouble(z);
byteBuf.writeFloat(yaw);
byteBuf.writeFloat(pitch);
byteBuf.writeByte(relativeMask);
if (v1_21_2) {
// Delta movement?
byteBuf.writeDouble(0);
byteBuf.writeDouble(0);
byteBuf.writeDouble(0);
byteBuf.writeFloat(yaw);
byteBuf.writeFloat(pitch);
byteBuf.writeInt(relativeMask);
} else {
byteBuf.writeFloat(yaw);
byteBuf.writeFloat(pitch);
byteBuf.writeByte(relativeMask);
}
if (protocolVersion.compareTo(MINECRAFT_1_8) > 0) {
writeVarInt(byteBuf, teleportId);
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_8)) {
if (!v1_21_2) {
ProtocolUtil.writeVarInt(byteBuf, teleportId);
}
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0
&& protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
byteBuf.writeBoolean(true); // Always dismount vehicle
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17)
&& protocolVersion.lessThanOrEquals(ProtocolVersion.MINECRAFT_1_19_3)) {
byteBuf.writeBoolean(dismountVehicle);
}
}
}
@@ -63,23 +78,29 @@ public final class SetPlayerPositionRotationPacket implements FallbackPacket {
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
x = byteBuf.readDouble();
y = byteBuf.readDouble();
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
// 1.7.2-1.7.10 send the minimum bounding box Y coordinate
byteBuf.readDouble();
}
z = byteBuf.readDouble();
yaw = byteBuf.readFloat();
pitch = byteBuf.readFloat();
onGround = byteBuf.readBoolean();
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_21_2)) {
short flag = byteBuf.readUnsignedByte();
onGround = (flag & 1) != 0;
horizontalCollision = (flag & 2) != 0;
} else {
onGround = byteBuf.readBoolean();
}
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
return protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 41 : 33;
public int expectedMaxLength(final @NotNull ProtocolVersion protocolVersion) {
return protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8) ? 41 : 33;
}
@Override
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMinLength(final ProtocolVersion protocolVersion) {
return 33;
}
}
@@ -32,7 +32,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@AllArgsConstructor
public final class SetPlayerRotationPacket implements FallbackPacket {
private float yaw, pitch;
private boolean onGround;
private boolean onGround, horizontalCollision;
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
@@ -43,6 +43,12 @@ public final class SetPlayerRotationPacket implements FallbackPacket {
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
yaw = byteBuf.readFloat();
pitch = byteBuf.readFloat();
onGround = byteBuf.readBoolean();
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_21_2)) {
short flag = byteBuf.readUnsignedByte();
onGround = (flag & 1) != 0;
horizontalCollision = (flag & 2) != 0;
} else {
onGround = byteBuf.readBoolean();
}
}
}
@@ -26,13 +26,10 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.entity.EntityType;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeUUID;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@NoArgsConstructor
@@ -46,21 +43,19 @@ public final class SpawnEntityPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
writeVarInt(byteBuf, entityId);
ProtocolUtil.writeVarInt(byteBuf, entityId);
final boolean v1_9orHigher = protocolVersion.compareTo(MINECRAFT_1_9) >= 0;
if (v1_9orHigher) {
writeUUID(byteBuf, UUID.randomUUID());
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)) {
ProtocolUtil.writeUUID(byteBuf, UUID.randomUUID());
}
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0) {
writeVarInt(byteBuf, entityType.getId(protocolVersion));
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_14)) {
ProtocolUtil.writeVarInt(byteBuf, entityType.getId().apply(protocolVersion));
} else {
byteBuf.writeByte(entityType.getId(protocolVersion));
byteBuf.writeByte(entityType.getId().apply(protocolVersion));
}
if (v1_9orHigher) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)) {
byteBuf.writeDouble(x);
byteBuf.writeDouble(y);
byteBuf.writeDouble(z);
@@ -73,14 +68,14 @@ public final class SpawnEntityPacket implements FallbackPacket {
byteBuf.writeByte(0); // pitch or yaw
byteBuf.writeByte(0); // yaw or pitch
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
byteBuf.writeByte(0); // head yaw
writeVarInt(byteBuf, data); // data
ProtocolUtil.writeVarInt(byteBuf, data); // data
} else {
byteBuf.writeInt(data); // data
}
if (v1_9orHigher || data > 0) {
if (data > 0 || protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)) {
byteBuf.writeShort((int) (velocityX * 8000D));
byteBuf.writeShort((int) (velocityY * 8000D));
byteBuf.writeShort((int) (velocityZ * 8000D));
@@ -25,13 +25,11 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.ComponentHolder;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@@ -48,38 +46,39 @@ public final class SystemChatPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
// Serialized message
componentHolder.write(byteBuf, protocolVersion);
componentHolder.write(byteBuf, protocolVersion, false);
// Type
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_1)) {
byteBuf.writeBoolean(false); // it's not the GAME_INFO type
} else if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
writeVarInt(byteBuf, 1); // system chat
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtil.writeVarInt(byteBuf, 1); // system chat
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeByte(1); // system chat
}
// Sender
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
&& protocolVersion.compareTo(MINECRAFT_1_19) < 0) {
writeUUID(byteBuf, UUID.randomUUID());
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtil.writeUUID(byteBuf, UUID.randomUUID());
}
}
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
message = readString(byteBuf, 256);
message = ProtocolUtil.readString(byteBuf, 256);
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
byteBuf.readLong(); // expiresAt
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
byteBuf.readLong(); // timestamp
if (protocolVersion.lessThanOrEquals(ProtocolVersion.MINECRAFT_1_19_1)) {
final long saltLong = byteBuf.readLong();
final byte[] signatureBytes = readByteArray(byteBuf);
final byte[] signatureBytes = ProtocolUtil.readByteArray(byteBuf);
boolean unsigned = false;
if (saltLong != 0L && signatureBytes.length > 0) {
// No need to store the valid signature
} else if ((protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0
} else if ((protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_1)
|| saltLong == 0L) && signatureBytes.length == 0) {
unsigned = true;
} else {
@@ -91,24 +90,23 @@ public final class SystemChatPacket implements FallbackPacket {
throw QuietDecoderException.INSTANCE;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
final int size = readVarInt(byteBuf);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_1)) {
final int size = ProtocolUtil.readVarInt(byteBuf);
if (size < 0 || size > 5) {
throw QuietDecoderException.INSTANCE;
}
for (int i = 0; i < size; i++) {
readUUID(byteBuf);
readByteArray(byteBuf);
ProtocolUtil.readUUID(byteBuf);
ProtocolUtil.readByteArray(byteBuf);
}
if (byteBuf.readBoolean()) {
readUUID(byteBuf);
readByteArray(byteBuf);
ProtocolUtil.readUUID(byteBuf);
ProtocolUtil.readByteArray(byteBuf);
}
}
} else {
byteBuf.readLong(); // timestamp
byteBuf.readLong(); // salt
final boolean signed = byteBuf.readBoolean();
@@ -117,7 +115,7 @@ public final class SystemChatPacket implements FallbackPacket {
byteBuf.readBytes(sign);
}
readVarInt(byteBuf);
ProtocolUtil.readVarInt(byteBuf);
final byte[] bytes = new byte[DIV_FLOOR];
byteBuf.readBytes(bytes);
}
@@ -25,10 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_9;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -41,13 +38,13 @@ public final class TeleportEntityPacket implements FallbackPacket {
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
writeVarInt(byteBuf, entityId);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtil.writeVarInt(byteBuf, entityId);
} else {
byteBuf.writeInt(entityId);
}
if (protocolVersion.compareTo(MINECRAFT_1_9) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)) {
byteBuf.writeDouble(x);
byteBuf.writeDouble(y);
byteBuf.writeDouble(z);
@@ -60,7 +57,7 @@ public final class TeleportEntityPacket implements FallbackPacket {
byteBuf.writeByte(0); // pitch or yaw
byteBuf.writeByte(0); // yaw or pitch
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeBoolean(onGround);
}
}
@@ -26,8 +26,6 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_17;
@Getter
@ToString
@NoArgsConstructor
@@ -38,7 +36,7 @@ public final class TransactionPacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_17)) {
windowId = byteBuf.readByte();
transactionId = byteBuf.readShort();
accepted = byteBuf.readBoolean();
@@ -52,7 +50,7 @@ public final class TransactionPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_17)) {
byteBuf.writeByte(windowId);
byteBuf.writeShort((short) transactionId);
byteBuf.writeBoolean(accepted);
@@ -25,9 +25,7 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeString;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -39,8 +37,8 @@ public final class TransferPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
writeString(byteBuf, destinationHost);
writeVarInt(byteBuf, destinationPort);
ProtocolUtil.writeString(byteBuf, destinationHost);
ProtocolUtil.writeVarInt(byteBuf, destinationPort);
}
@Override
@@ -26,10 +26,7 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarLong;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@NoArgsConstructor
@@ -41,28 +38,28 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
byteBuf.writeInt(sectionX);
byteBuf.writeInt(sectionZ);
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeShort(blockUpdates.length);
byteBuf.writeInt(4 * blockUpdates.length);
} else {
writeVarInt(byteBuf, blockUpdates.length);
ProtocolUtil.writeVarInt(byteBuf, blockUpdates.length);
}
for (final BlockUpdate block : blockUpdates) {
byteBuf.writeShort(block.getLegacyBlockState());
final int blockId = block.getBlockType().getId(protocolVersion);
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
writeVarInt(byteBuf, blockId);
final int blockId = block.getBlockType().getId().apply(protocolVersion);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_13)) {
ProtocolUtil.writeVarInt(byteBuf, blockId);
} else {
final int shiftedBlockId = blockId << 4;
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeShort(shiftedBlockId);
} else {
writeVarInt(byteBuf, shiftedBlockId);
ProtocolUtil.writeVarInt(byteBuf, shiftedBlockId);
}
}
}
@@ -74,17 +71,17 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
byteBuf.writeLong(((sectionX & 0x3FFFFFL) << 42) | (sectionY & 0xFFFFF) | ((sectionZ & 0x3FFFFFL) << 20));
// 1.20+ don't have light update suppression
if (protocolVersion.compareTo(MINECRAFT_1_20) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20)) {
byteBuf.writeBoolean(true); // suppress light updates
}
writeVarInt(byteBuf, blockUpdates.length);
ProtocolUtil.writeVarInt(byteBuf, blockUpdates.length);
for (final BlockUpdate block : blockUpdates) {
// https://wiki.vg/Protocol#Update_Section_Blocks
final int shiftedBlockId = block.getBlockType().getId(protocolVersion) << 12;
final int shiftedBlockId = block.getBlockType().getId().apply(protocolVersion) << 12;
final long positionIdValue = shiftedBlockId | block.getBlockState();
writeVarLong(byteBuf, positionIdValue);
ProtocolUtil.writeVarLong(byteBuf, positionIdValue);
}
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 Sonar 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 xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public final class UpdateTimePacket implements FallbackPacket {
private long worldAge, timeOfDay;
private boolean tickDayTime;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
byteBuf.writeLong(worldAge);
byteBuf.writeLong(timeOfDay);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
byteBuf.writeBoolean(tickDayTime);
}
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
}
@@ -26,21 +26,21 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.ratelimit.Ratelimiter;
import java.net.InetAddress;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
// Idea taken from Velocity
@Setter
@RequiredArgsConstructor
public final class CaffeineCacheRatelimiter implements Ratelimiter<InetAddress> {
private Cache<InetAddress, Long> expiringCache;
private long timeout;
private Cache<InetAddress, Long> expiringCache;
public CaffeineCacheRatelimiter(final long timeout, final @NotNull TimeUnit unit) {
public CaffeineCacheRatelimiter(final @NotNull Duration duration) {
this.timeout = duration.toNanos();
this.expiringCache = Caffeine.newBuilder()
.ticker(Ticker.systemTicker())
.expireAfterWrite(timeout, unit)
.expireAfterWrite(duration)
.build();
this.timeout = unit.toNanos(timeout);
}
@Override
@@ -20,11 +20,9 @@ package xyz.jonesdev.sonar.common.fallback.verification;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.common.fallback.protocol.CaptchaPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.ItemType;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.MapCaptchaInfo;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetContainerSlotPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.map.MapCaptchaInfo;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetPlayerPositionPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetPlayerPositionRotationPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SystemChatPacket;
@@ -45,10 +43,9 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
this.tries = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
// If the player is on Java, set the 5th slot (ID 4) in the player's hotbar to the map
// If the player is on Bedrock, set the 1st slot (ID 0) in the player's hotbar to the map
final int slotId = user.isGeyser() ? 36 : 40;
user.delayedWrite(new SetContainerSlotPacket(0, slotId, 1, ItemType.FILLED_MAP, MAP_ITEM_NBT));
// We may not be on slot 4, so we need to make sure to synchronize the current slot
user.delayedWrite(CAPTCHA_HELD_ITEM_SLOT);
user.delayedWrite(CAPTCHA_SET_CONTAINER_SLOT);
// Send random captcha to the player
final MapCaptchaInfo captcha = CaptchaPreparer.getRandomCaptcha();
this.answer = captcha.getAnswer().toLowerCase();
@@ -60,7 +57,7 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
// Make sure the player knows that they have to enter the code in chat
user.delayedWrite(enterCodeMessage);
// Send all packets in one flush
user.getChannel().flush();
user.channel().flush();
}
private final String answer;
@@ -99,7 +96,7 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
if (keepAliveStreak++ > 20) {
keepAliveStreak = 0;
// Send a KeepAlive packet to prevent timeout
user.write(CAPTCHA_KEEP_ALIVE);
user.write(RANDOM_KEEP_ALIVE);
}
}
}
@@ -20,12 +20,12 @@ package xyz.jonesdev.sonar.common.fallback.verification;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import static xyz.jonesdev.sonar.api.config.SonarConfiguration.Verification.Gamemode.CREATIVE;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
public final class FallbackGravityHandler extends FallbackVerificationHandler {
@@ -49,11 +49,11 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
user.delayedWrite(DEFAULT_ABILITIES);
}
// Write the DefaultSpawnPosition packet to the buffer
if (user.getProtocolVersion().compareTo(MINECRAFT_1_19_3) >= 0) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_3)) {
user.delayedWrite(defaultSpawnPosition);
}
// Teleport the player to the position where we're starting to check them
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
user.delayedWrite(spawnPosition);
user.delayedWrite(fallStartPosition);
} else {
@@ -62,7 +62,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
}
// 1.20.3+ introduced game events
// Make sure the client knows that we're sending chunks next
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_3) >= 0) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_3)) {
user.delayedWrite(START_WRITING_CHUNKS);
}
// Teleport player into an empty world by sending an empty chunk packet
@@ -70,14 +70,18 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
// Spawn the invisible platform below the player
if (enableCollisionsCheck) {
final int index = RANDOM.nextInt(BLOCKS_PACKETS.length);
blockHeight = POSSIBLE_BLOCK_TYPES[index].getBlockHeight();
blockHeight = POSSIBLE_BLOCK_TYPES[index].getBlockHeight().apply(user.getProtocolVersion());
user.delayedWrite(BLOCKS_PACKETS[index]);
}
// Update the world time, if necessary
if (updateTime != null) {
user.delayedWrite(updateTime);
}
// Send all packets at once
user.getChannel().flush();
user.channel().flush();
// 1.8 and below don't have TeleportConfirm packets, which is why we're skipping that check.
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_9)) {
// Enable the movement checks
teleported = true;
}
@@ -88,26 +92,17 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
private boolean teleported, canFall, checkMovement;
private double y, deltaY, blockHeight;
private int movementTick, expectedTeleportId = FIRST_TELEPORT_ID;
private void markSuccess() {
// Force-stop the movement checks
teleported = false;
// Exempt pre-1.20.2 since they've already passed that check in the configuration phase
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) < 0) {
preJoinHandler.validateClientInformation();
}
// Send the player to the next verification handler
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
decoder.setListener(new FallbackProtocolHandler(user));
}
private SetPlayerPositionRotationPacket lastPositionPacket;
@Override
public void handle(final @NotNull FallbackPacket packet) {
if (packet instanceof SetPlayerPositionRotationPacket) {
// Make sure the player has teleported before checking for position packets
final SetPlayerPositionRotationPacket position = (SetPlayerPositionRotationPacket) packet;
if (teleported) {
final SetPlayerPositionRotationPacket position = (SetPlayerPositionRotationPacket) packet;
handleMovement(position.getX(), position.getY(), position.getZ(), position.isOnGround(), true);
} else if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
lastPositionPacket = position;
}
} else if (packet instanceof SetPlayerPositionPacket) {
// Make sure the player has teleported before checking for position packets
@@ -116,29 +111,48 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
handleMovement(position.getX(), position.getY(), position.getZ(), position.isOnGround(), false);
}
} else if (packet instanceof ConfirmTeleportationPacket) {
final ConfirmTeleportationPacket teleportConfirm = (ConfirmTeleportationPacket) packet;
final ConfirmTeleportationPacket confirmTeleport = (ConfirmTeleportationPacket) packet;
// Only expect this packet to be sent once
checkState(!teleported, "duplicate teleport confirm");
// Check if the teleport ID matches the expected ID
final int teleportId = teleportConfirm.getTeleportId();
checkState(teleportId == expectedTeleportId,
"expected TP ID " + expectedTeleportId + ", but got " + teleportId);
checkState(confirmTeleport.getTeleportId() == expectedTeleportId,
"expected TP ID " + expectedTeleportId + ", but got " + confirmTeleport.getTeleportId());
// The first teleport ID is not useful for us in this context, skip it
if (expectedTeleportId == FIRST_TELEPORT_ID) {
lastPositionPacket = null;
expectedTeleportId = SECOND_TELEPORT_ID;
} else {
// Enable the movement checks
teleported = true;
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
checkState(lastPositionPacket != null, "excepted position rotation but got teleport confirm.");
handleMovement(
lastPositionPacket.getX(), lastPositionPacket.getY(), lastPositionPacket.getZ(),
lastPositionPacket.isOnGround(), true);
}
}
} else if (packet instanceof ClientInformationPacket
|| packet instanceof PluginMessagePacket) {
// Pass these packets back to the login handler
// TODO: recode this
preJoinHandler.handle(packet);
}
}
private void markSuccess() {
// Force-stop the movement checks
teleported = false;
// Exempt pre-1.20.2 since they've already passed that check in the configuration phase
if (!user.isGeyser() && user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
preJoinHandler.validateClientInformation();
}
// Send the player to the next verification handler
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackProtocolHandler(user));
}
private void handleMovement(final double x, final double y, final double z,
final boolean onGround, final boolean rotated) {
if (!checkMovement) {
@@ -157,6 +171,11 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
// Synchronize the Y coordinate
this.y = dynamicSpawnYPosition;
checkMovement = true;
// Send the welcome message to the player
if (welcomeMessage != null) {
user.write(welcomeMessage);
}
return;
}
@@ -173,7 +192,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
}
// Ensure that the player's Y coordinate is above the collision platform
checkState(y >= DEFAULT_Y_COLLIDE_POSITION,
checkState(y >= PLATFORM_Y_POSITION,
"fell through blocks: " + y + "/" + deltaY + "/" + movementTick);
// The player is not allowed to move away from the collision platform.
@@ -188,7 +207,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
checkState(rotated, "illegal movement packet order: " + deltaY);
checkState(movementTick == 0, "illegal y motion: " + movementTick);
// 1.7 clients immediately start falling after this packet
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_8)) {
movementTick++;
}
// We've received the first position packet; the player will now start falling
@@ -224,7 +243,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
failOrShowCaptcha("illegal collision tick: " + movementTick + "/" + blockHeight);
}
// Calculate the difference between the player's Y coordinate and the expected Y coordinate
final double collisionOffsetY = (DEFAULT_Y_COLLIDE_POSITION + blockHeight) - y;
final double collisionOffsetY = (PLATFORM_Y_POSITION + blockHeight) - y;
// Make sure the player is actually colliding with the blocks and not only spoofing ground
if (collisionOffsetY != 0) {
failOrShowCaptcha("illegal collision: " + collisionOffsetY + "/" + y + "/" + blockHeight);
@@ -20,6 +20,7 @@ package xyz.jonesdev.sonar.common.fallback.verification;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
@@ -34,10 +35,7 @@ import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.BRAND_CHANNEL;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.BRAND_CHANNEL_LEGACY;
public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
@@ -45,55 +43,24 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
super(user);
// Start initializing the actual join process for pre-1.20.2 clients
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) < 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
// This trick helps in reducing unnecessary outgoing server traffic
// by avoiding sending other packets to clients that are potentially bots.
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
user.getChannel().eventLoop().schedule(this::markSuccess, 100L, TimeUnit.MILLISECONDS);
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_8)) {
user.channel().eventLoop().schedule(this::markSuccess, 100L, TimeUnit.MILLISECONDS);
} else {
sendKeepAlive();
/*
* The purpose of this KeepAlive packet is to confirm that the connection
* is active and legitimate, thereby preventing bot connections that
* could flood the server with login attempts and other unwanted traffic.
*/
user.write(PRE_JOIN_KEEP_ALIVE);
}
}
}
private boolean receivedClientInfo, receivedClientBrand, acknowledgedLogin;
private int expectedKeepAliveId;
/**
* The purpose of these KeepAlive packets is to confirm that the connection
* is active and legitimate, thereby preventing bot connections that
* could flood the server with login attempts and other unwanted traffic.
*/
private void sendKeepAlive() {
// Send a KeepAlive packet with a random ID
expectedKeepAliveId = RANDOM.nextInt();
user.write(new KeepAlivePacket(expectedKeepAliveId));
}
private void markAcknowledged() {
acknowledgedLogin = true;
// Update state, so we're able to send/receive configuration packets
updateEncoderDecoderState(FallbackPacketRegistry.CONFIG);
synchronizeClientRegistry();
// Write the FinishConfiguration packet to the buffer
user.delayedWrite(FINISH_CONFIGURATION);
// Send all packets in one flush
user.getChannel().flush();
}
private void markSuccess() {
// Pass the player to the next verification handler
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
decoder.setListener(new FallbackGravityHandler(user, this));
}
void validateClientInformation() {
checkState(receivedClientInfo, "didn't send client settings");
// Don't check Geyser players for plugin messages as they don't have them
if (!user.isGeyser()) {
checkState(receivedClientBrand, "didn't send plugin message");
}
}
private int expectedKeepAliveId = PRE_JOIN_KEEP_ALIVE_ID;
@Override
public void handle(final @NotNull FallbackPacket packet) {
@@ -106,26 +73,39 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
checkState(keepAliveId == expectedKeepAliveId,
"expected K ID " + expectedKeepAliveId + " but got " + keepAliveId);
// Immediately verify the player if they do not need any configuration (pre-1.20.2)
if (expectedKeepAliveId != 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
markSuccess();
} else {
markAcknowledged();
}
}
// 1.8 clients send KeepAlive packets with the ID 0 every second
// while the player is in the "Downloading terrain" screen.
expectedKeepAliveId = 0;
// Immediately verify the player if they do not need any configuration (pre-1.20.2)
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) < 0) {
markSuccess();
}
} else if (packet instanceof LoginAcknowledgedPacket) {
// Prevent users from sending multiple LoginAcknowledged packets
checkState(!acknowledgedLogin, "sent duplicate login ack");
markAcknowledged();
// Update state, so we're able to send/receive packets during the CONFIG state
updateEncoderDecoderState(FallbackPacketRegistry.CONFIG);
// Perform the KeepAlive check now (config, not pre-config)
user.write(PRE_JOIN_KEEP_ALIVE);
} else if (packet instanceof FinishConfigurationPacket) {
validateClientInformation();
// Update the encoder and decoder state because we're currently in the CONFIG state
updateEncoderDecoderState(FallbackPacketRegistry.GAME);
if (!user.isGeyser()) {
validateClientInformation();
}
markSuccess();
} else if (packet instanceof ClientInformationPacket) {
final ClientInformationPacket clientInformation = (ClientInformationPacket) packet;
if (!user.isGeyser()) {
checkState(clientInformation.getViewDistance() >= 2,
"view distance: " + clientInformation.getViewDistance());
}
// Ensure that the client locale is correct
validateClientLocale(clientInformation.getLocale());
// Check if the player sent an unused bit flag in the skin section
@@ -137,8 +117,8 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
} else if (packet instanceof PluginMessagePacket) {
final PluginMessagePacket pluginMessage = (PluginMessagePacket) packet;
final boolean usingModernChannel = pluginMessage.getChannel().equals(BRAND_CHANNEL);
final boolean usingLegacyChannel = pluginMessage.getChannel().equals(BRAND_CHANNEL_LEGACY);
final boolean usingModernChannel = pluginMessage.getChannel().equals("minecraft:brand");
final boolean usingLegacyChannel = pluginMessage.getChannel().equals("MC|Brand");
// Skip this payload if it does not contain client brand information
if (!usingModernChannel && !usingLegacyChannel) {
@@ -150,7 +130,8 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
// Check if the channel is correct - 1.13 uses the new namespace
// system ('minecraft:' + channel) and anything below 1.13 uses
// the legacy namespace system ('MC|' + channel).
checkState(usingLegacyChannel || user.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0,
checkState(usingLegacyChannel
|| user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_13),
"illegal PluginMessage channel: " + pluginMessage.getChannel());
// Validate the client branding using a regex to filter unwanted characters.
@@ -162,26 +143,37 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
}
}
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
// Update the packet registry state in the encoder and decoder pipelines
user.getPipeline().get(FallbackPacketDecoder.class).updateRegistry(registry);
user.getPipeline().get(FallbackPacketEncoder.class).updateRegistry(registry);
private void markAcknowledged() {
acknowledgedLogin = true;
// Write the new RegistrySync packets to the buffer
for (final FallbackPacket packet : getRegistryPackets(user.getProtocolVersion())) {
user.delayedWrite(packet);
}
// Write the FinishConfiguration packet to the buffer
user.delayedWrite(FINISH_CONFIGURATION);
// Send all packets in one flush
user.channel().flush();
}
private void synchronizeClientRegistry() {
// 1.20.5+ adds new "game bundle features" which overcomplicate all of this...
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
// Write the new RegistrySync packets to the buffer
for (final FallbackPacket syncPacket : user.getProtocolVersion().compareTo(MINECRAFT_1_21) < 0
? REGISTRY_SYNC_1_20_5 : REGISTRY_SYNC_1_21) {
user.delayedWrite(syncPacket);
}
} else {
// Write the old RegistrySync packet to the buffer
user.delayedWrite(REGISTRY_SYNC_LEGACY);
private void markSuccess() {
if (user.channel().isActive()) {
// Pass the player to the next verification handler
final FallbackGravityHandler gravityHandler = new FallbackGravityHandler(user, this);
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(gravityHandler);
}
}
void validateClientInformation() {
checkState(receivedClientInfo, "didn't send client settings");
checkState(receivedClientBrand, "didn't send plugin message");
}
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
// Update the packet registry state in the encoder and decoder pipelines
user.channel().pipeline().get(FallbackPacketDecoder.class).updateRegistry(registry);
user.channel().pipeline().get(FallbackPacketEncoder.class).updateRegistry(registry);
}
private void validateClientBrand(final byte @NotNull [] data) {
// Check if the client brand is too short. It has to have at least 2 bytes.
checkState(data.length > 1, "client brand is too short");
@@ -191,7 +183,7 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
// https://discord.com/channels/923308209769426994/1116066363887321199/1256929441053933608
String brand = new String(data, StandardCharsets.UTF_8);
// Remove the invalid character at the beginning of the client brand
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
brand = brand.substring(1);
}
// Check for illegal client brands
@@ -20,6 +20,7 @@ package xyz.jonesdev.sonar.common.fallback.verification;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.AnimationPacket;
@@ -27,8 +28,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.EntityAnimationP
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetHeldItemPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.TransactionPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.CACHED_HELD_ITEM_SLOT;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.INVALID_HELD_ITEM_SLOT;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.PLAYER_ENTITY_ID;
public final class FallbackProtocolHandler extends FallbackVerificationHandler {
@@ -40,77 +40,18 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
sendTransaction();
}
private boolean waitingSwingArm, waitingSlotConfirm;
private boolean waitingSwingArm, waitingSlotConfirm, waitingTransaction;
private short expectedTransactionId;
private int currentClientSlotId, expectedSlotId = -1;
/**
* Uses Transaction (ping) packets to check for an immediate, legitimate response from the client
* <br>
* <a href="https://wiki.vg/Protocol#Ping_.28configuration.29">Wiki.vg - Ping (configuration)</a>
* <a href="https://wiki.vg/Protocol#Ping_.28play.29">Wiki.vg - Ping (play)</a>
*/
private void sendTransaction() {
// Send a Transaction (Ping) packet with a random ID
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
user.write(new TransactionPacket(0, expectedTransactionId, false));
}
/**
* Uses SetHeldItem packets to check for a legitimate response from the client
* <br>
* <a href="https://wiki.vg/Protocol#Set_Held_Item_.28serverbound.29">Wiki.vg - SetHeldItem (play)</a>
*/
private void sendSetHeldItem() {
// Move the player's slot by 4 (slot limit divided by 2),
// and then modulo it by the slot limit (8) to ensure that we don't send invalid slot IDs.
expectedSlotId = (currentClientSlotId + 4) % 8;
// Send a HeldItemChange packet to the player to see if the player responds
user.delayedWrite(CACHED_HELD_ITEM_SLOT);
// Send two SetHeldItem packets with the same slot to check if the player responds with the correct slot.
// By vanilla protocol, the client does not respond to duplicate SetHeldItem packets.
// We can take advantage of this by sending two packets with the same content to check for a valid response.
final SetHeldItemPacket heldItemPacket = new SetHeldItemPacket(expectedSlotId);
user.delayedWrite(heldItemPacket);
user.delayedWrite(heldItemPacket);
user.getChannel().flush();
}
/**
* Uses EntityAnimation packets to check for the excepted Animation (SwingArm) from the client.
* <br>
* <a href="https://wiki.vg/Protocol#Entity_Animation">Wiki.vg - EntityAnimation</a>
* <a href="https://wiki.vg/Protocol#Swing_Arm">Wiki.vg - SwingArm</a>
*/
private void sendArmAnimation() {
waitingSwingArm = true;
user.write(new EntityAnimationPacket(PLAYER_ENTITY_ID, EntityAnimationPacket.Type.SWING_MAIN_ARM));
}
private void markSuccess() {
// Either send the player to the vehicle check,
// send the player to the CAPTCHA, or finish the verification.
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
// Pass the player to the next best verification handler
if (Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
decoder.setListener(new FallbackVehicleHandler(user));
} else if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
decoder.setListener(new FallbackCaptchaHandler(user));
} else {
// The player has passed all checks
finishVerification();
}
}
@Override
public void handle(final @NotNull FallbackPacket packet) {
if (packet instanceof TransactionPacket) {
final TransactionPacket transaction = (TransactionPacket) packet;
// Make sure random transactions aren't counted
checkState(expectedTransactionId <= 0, "unexpected transaction");
// Make sure we are expecting a transaction packet
checkState(waitingTransaction, "unexpected transaction: " + transaction.getTransactionId());
// Make sure the window ID is valid
checkState(transaction.getWindowId() == 0, "wrong window ID " + transaction.getWindowId());
checkState(transaction.getWindowId() == 0, "wrong window: " + transaction.getWindowId());
// Make sure the transaction was accepted
// This must - by vanilla protocol - always be accepted
checkState(transaction.isAccepted(), "didn't accept transaction");
@@ -138,7 +79,7 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
sendSetHeldItem();
}
expectedTransactionId = 0;
waitingTransaction = false;
} else if (packet instanceof SetHeldItemPacket) {
final SetHeldItemPacket heldItemPacket = (SetHeldItemPacket) packet;
@@ -146,10 +87,9 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
// Also check if the player sent an invalid slot which is impossible by vanilla protocol
checkState(slotId >= 0 && slotId <= 8, "slot out of range: " + slotId);
// Check if the player sent a duplicate slot packet which is impossible by vanilla protocol
checkState(slotId != currentClientSlotId, "invalid slot: " + slotId);
checkState(slotId != currentClientSlotId, "duplicate slot: " + slotId);
// Only continue checking if we're actually expecting a SetHeldItem packet
// The player can send a SetHeldItem packet by themselves -> exempt
if (expectedSlotId != -1
// Check if the slot ID matches the expected slot ID
// This can false flag if a player spams these packets, which is why we don't fail for this
@@ -165,21 +105,78 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
// Make sure we are awaiting an AnimationPacket packet
if (waitingSwingArm) {
final AnimationPacket animationPacket = (AnimationPacket) packet;
checkState(animationPacket.getHand() == AnimationPacket.MAIN_HAND,
"invalid hand " + animationPacket.getHand());
// Check that the 1.7 client is responding to the correct entity id and animation type.
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
// Check entity id is player itself and if the player is sending the correct animation type
if (animationPacket.getEntityId() == PLAYER_ENTITY_ID
&& animationPacket.getType() == AnimationPacket.LegacyAnimationType.SWING_ARM) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_8)) {
// Check if the entity ID is the player itself
checkState(animationPacket.getEntityId() == PLAYER_ENTITY_ID,
"invalid entity ID " + animationPacket.getEntityId());
// Check if the player is sending the correct animation type
if (animationPacket.getType() == AnimationPacket.LegacyAnimationType.SWING_ARM) {
markSuccess();
waitingSwingArm = false;
}
} else {
checkState(animationPacket.getHand() == AnimationPacket.MAIN_HAND,
"invalid hand " + animationPacket.getHand());
markSuccess();
waitingSwingArm = false;
}
}
}
}
/**
* Uses Transaction (ping) packets to check for an immediate, legitimate response from the client
* <br>
* <a href="https://wiki.vg/Protocol#Ping_.28configuration.29">Wiki.vg - Ping (configuration)</a>
* <a href="https://wiki.vg/Protocol#Ping_.28play.29">Wiki.vg - Ping (play)</a>
*/
private void sendTransaction() {
// Send a Transaction (Ping) packet with a random ID
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
user.write(new TransactionPacket(0, expectedTransactionId, false));
waitingTransaction = true;
}
/**
* Uses SetHeldItem packets to check for a legitimate response from the client
* <br>
* <a href="https://wiki.vg/Protocol#Set_Held_Item_.28serverbound.29">Wiki.vg - SetHeldItem (play)</a>
*/
private void sendSetHeldItem() {
// Send an invalid HeldItemChange packet to the player to see if the player responds at all
user.delayedWrite(INVALID_HELD_ITEM_SLOT);
// Increment the player's slot by a random slot, and then modulo it by the maximum slot (8)
expectedSlotId = (currentClientSlotId + 1 + RANDOM.nextInt(7)) % 8;
// Send two SetHeldItem packets with the same slot to check if the player responds with the correct slot.
// By vanilla protocol, the client does not respond to duplicate SetHeldItem packets.
// We can take advantage of this by sending two packets with the same content to check for a valid response.
final SetHeldItemPacket heldItemPacket = new SetHeldItemPacket(expectedSlotId);
user.delayedWrite(heldItemPacket);
user.delayedWrite(heldItemPacket);
user.channel().flush();
}
/**
* Uses EntityAnimation packets to check for the excepted Animation (SwingArm) from the client.
* <br>
* <a href="https://wiki.vg/Protocol#Entity_Animation">Wiki.vg - EntityAnimation</a>
* <a href="https://wiki.vg/Protocol#Swing_Arm">Wiki.vg - SwingArm</a>
*/
private void sendArmAnimation() {
user.write(new EntityAnimationPacket(PLAYER_ENTITY_ID, EntityAnimationPacket.Type.SWING_MAIN_ARM));
waitingSwingArm = true;
}
private void markSuccess() {
// Pass the player to the next best verification handler
if (!user.isGeyser() && Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackVehicleHandler(user));
} else if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackCaptchaHandler(user));
} else {
// The player has passed all checks
finishVerification();
}
}
}
@@ -17,14 +17,15 @@
package xyz.jonesdev.sonar.common.fallback.verification;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_9;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
public final class FallbackVehicleHandler extends FallbackVerificationHandler {
@@ -32,191 +33,164 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
public FallbackVehicleHandler(final @NotNull FallbackUser user) {
super(user);
// Send the necessary packets to mount the player on the boat vehicle
user.delayedWrite(spawnBoatEntity);
user.delayedWrite(setBoatPassengers);
user.getChannel().flush();
spawnVehicle(State.IN_BOAT);
}
private int expectedTransactionId;
private int paddlePackets, inputPackets, positionPackets, rotationPackets, vehicleMovePackets;
private boolean expectMovement, inVoid, inMinecart, waitingSpawnMinecart, waitingRemoveMinecart;
private double minecartMotion, minecartY = IN_AIR_Y_POSITION;
private boolean waitingForStateChange;
private State state = State.WAITING;
private State nextState;
private int expectedKeepAliveId;
private int rotations, inputs, paddles, vehicleMoves;
private double boatMotion, boatY = IN_AIR_Y_POSITION;
private void markSuccess() {
// Pass the player to the next best verification handler
if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
// Either send the player to the CAPTCHA or finish the verification.
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
// Send the player to the CAPTCHA handler
decoder.setListener(new FallbackCaptchaHandler(user));
} else {
// The player has passed all checks
finishVerification();
}
}
@RequiredArgsConstructor
private enum State {
WAITING(false),
IN_BOAT(true),
IN_AIR_AFTER_BOAT(false),
IN_MINECART(true),
IN_AIR_AFTER_MINECART(false);
// We can abuse the entity remove mechanic and check for position packets when the entity dies
private void handleMovement(final double y, final boolean onGround) {
// The player cannot be on the ground if the Y position is less than 0
checkState(!onGround || !inVoid || waitingRemoveMinecart, "invalid ground state: " + y);
// Check the Y position of the player
final double minimumY = inVoid ? IN_VOID_Y_POSITION : IN_AIR_Y_POSITION;
checkState(y <= minimumY, "illegal y position: " + y + "/" + minimumY);
if (!waitingRemoveMinecart && !waitingSpawnMinecart
&& positionPackets++ > Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets()) {
if (user.isGeyser() || inVoid) {
// Mark this check as successful if the player sent a few position packets
markSuccess();
} else if (!inMinecart) {
// Continue checking the player by using Minecarts
spawnMinecart();
}
}
}
private void handleRotation() {
// Once the player sent enough packets, go to the next stage
final int minimumPackets = Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets();
if (paddlePackets > minimumPackets
&& inputPackets > minimumPackets
&& rotationPackets > minimumPackets
&& vehicleMovePackets > minimumPackets) {
// If the player is riding a Minecart, teleport the entity to < -100
// to see how the player reacts to the invalid position of the entity.
if (inMinecart) {
user.delayedWrite(teleportMinecart);
// We're using transactions to avoid false positives with lag
waitingRemoveMinecart = true;
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
user.delayedWrite(new TransactionPacket(0, expectedTransactionId, false));
user.getChannel().flush();
} else {
// If the player is riding a boat, remove the entity
// The next Y coordinate the player will send is going
// to be the vehicle spawn position (64 in this case).
user.write(removeBoat);
}
// Listen for next movement packet(s)
expectMovement = true;
}
rotationPackets++;
}
private void spawnMinecart() {
expectMovement = false;
paddlePackets = inputPackets = positionPackets = rotationPackets = 0;
// Use transaction packets to confirm that the entity has spawned
waitingSpawnMinecart = true;
minecartMotion = 0.03999999910593033D * 2 * -1;
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
user.delayedWrite(new TransactionPacket(0, expectedTransactionId, false));
user.delayedWrite(spawnMinecartEntity);
user.delayedWrite(setMinecartPassengers);
user.getChannel().flush();
private final boolean inVehicle;
}
@Override
public void handle(final @NotNull FallbackPacket packet) {
if (packet instanceof TransactionPacket) {
final TransactionPacket transaction = (TransactionPacket) packet;
// Make sure random transactions aren't counted
checkState(expectedTransactionId <= 0, "unexpected transaction");
// Make sure the window ID is valid
checkState(transaction.getWindowId() == 0, "wrong window ID " + transaction.getWindowId());
// Make sure the transaction was accepted
// This must - by vanilla protocol - always be accepted
checkState(transaction.isAccepted(), "didn't accept transaction");
// Also check if the transaction ID matches the expected ID
checkState(expectedTransactionId == transaction.getTransactionId(),
"expected T ID " + expectedTransactionId + ", but got " + transaction.getTransactionId());
if (packet instanceof KeepAlivePacket) {
final KeepAlivePacket keepAlivePacket = (KeepAlivePacket) packet;
// We're "lag compensating" the Minecart to avoid false positives
if (waitingSpawnMinecart) {
waitingSpawnMinecart = false;
inMinecart = true;
} else if (waitingRemoveMinecart) {
waitingRemoveMinecart = inMinecart = false;
inVoid = true;
positionPackets = 0;
}
// Check if we are expecting a KeepAlive packet
checkState(nextState != null, "invalid packet timing");
// Also check if the KeepAlive ID matches the expected ID
checkState(keepAlivePacket.getId() == expectedKeepAliveId,
"expected K ID " + expectedKeepAliveId + ", but got " + keepAlivePacket.getId());
expectedTransactionId = 0;
} else if (packet instanceof SetPlayerPositionRotationPacket) {
if (expectMovement) {
state = nextState;
nextState = null;
waitingForStateChange = false;
} else if (!waitingForStateChange) {
if (packet instanceof PaddleBoatPacket) {
if (state == State.IN_BOAT) {
paddles++;
}
} else if (packet instanceof VehicleMovePacket) {
if (state == State.IN_BOAT) {
final VehicleMovePacket vehicleMove = (VehicleMovePacket) packet;
// Check the Y position of the vehicle
checkState(vehicleMove.getY() <= IN_AIR_Y_POSITION, "bad vehicle y: " + vehicleMove.getY());
// Check the gravity of the vehicle
final double lastBoatMotion = boatMotion;
final double lastBoatY = boatY;
boatY = vehicleMove.getY();
boatMotion = boatY - lastBoatY;
final double predicted = lastBoatMotion - 0.03999999910593033D;
final double difference = Math.abs(boatMotion - predicted);
// Check if the difference between the predicted and actual motion is too large
checkState(difference < 1e-7, "bad vehicle gravity: " + predicted + "/" + boatMotion);
vehicleMoves++;
}
} else if (packet instanceof SetPlayerRotationPacket) {
if (state.inVehicle) {
rotations++;
// 1.21.2+ do not send PlayerInput packets when inside a vehicle.
// Handle it after SetPlayerRotationPacket to simulate vanilla behavior.
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
handlePlayerInput();
}
}
} else if (packet instanceof PlayerInputPacket) {
// 1.21.2+ send PlayerInput packets when the player starts sprinting, sneaking, etc.
if (state.inVehicle && user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
final PlayerInputPacket playerInput = (PlayerInputPacket) packet;
// Check if the player is sending invalid vehicle speed values
final float forward = Math.abs(playerInput.getForward());
final float sideways = Math.abs(playerInput.getSideways());
final float maxVehicleSpeed = /*user.isGeyser() ? 1 :*/ 0.98f;
checkState(forward <= maxVehicleSpeed, "illegal speed (f): " + forward);
checkState(sideways <= maxVehicleSpeed, "illegal speed (s): " + sideways);
handlePlayerInput();
}
} else if (packet instanceof SetPlayerPositionRotationPacket) {
final SetPlayerPositionRotationPacket posRot = (SetPlayerPositionRotationPacket) packet;
handleMovement(posRot.getY(), posRot.isOnGround());
}
} else if (packet instanceof SetPlayerPositionPacket) {
if (expectMovement) {
} else if (packet instanceof SetPlayerPositionPacket) {
final SetPlayerPositionPacket position = (SetPlayerPositionPacket) packet;
handleMovement(position.getY(), position.isOnGround());
}
} else if (packet instanceof SetPlayerRotationPacket) {
if (!expectMovement && !user.isGeyser()) {
handleRotation();
}
} else if (packet instanceof PaddleBoatPacket) {
checkState(!inMinecart, "invalid packet order (unexpected PaddleBoatPacket)");
paddlePackets++;
} else if (packet instanceof VehicleMovePacket) {
checkState(!inMinecart, "invalid packet order (unexpected VehicleMovePacket)");
final VehicleMovePacket vehicleMove = (VehicleMovePacket) packet;
if (!user.isGeyser()) {
// Check the Y position of the vehicle
checkState(vehicleMove.getY() <= IN_AIR_Y_POSITION,
"invalid vehicle y position: " + vehicleMove.getY());
// Predict the Y motion of the minecart
final double lastMinecartMotion = minecartMotion;
final double lastMinecartY = minecartY;
minecartY = vehicleMove.getY();
minecartMotion = minecartY - lastMinecartY;
final double predicted = lastMinecartMotion - 0.03999999910593033D;
final double difference = Math.abs(minecartMotion - predicted);
// Check if the difference between the predicted and actual motion is too large
checkState(difference < 1e-7,
"invalid vehicle gravity: " + predicted + "/" + minecartMotion + "/" + difference);
}
vehicleMovePackets++;
} else if (packet instanceof PlayerInputPacket) {
if (!expectMovement) {
final PlayerInputPacket playerInput = (PlayerInputPacket) packet;
}
}
// Check if the player is sending invalid vehicle speed values
final float forward = Math.abs(playerInput.getForward());
final float sideways = Math.abs(playerInput.getSideways());
final float maxVehicleSpeed = user.isGeyser() ? 1 : 0.98f;
checkState(forward <= maxVehicleSpeed, "illegal speed (f): " + forward);
checkState(sideways <= maxVehicleSpeed, "illegal speed (s): " + sideways);
private void spawnVehicle(final @NotNull State nextState) {
user.delayedWrite(nextState == State.IN_BOAT ? SPAWN_BOAT_ENTITY : SPAWN_MINECART_ENTITY);
user.delayedWrite(SET_VEHICLE_PASSENGERS);
prepareForNextState(nextState);
}
// Only mark this packet as correct if the player is not moving the vehicle
if (playerInput.isJump() || playerInput.isUnmount()) {
return;
}
private void prepareForNextState(final @NotNull State nextState) {
this.nextState = nextState;
waitingForStateChange = true;
expectedKeepAliveId = RANDOM.nextInt();
rotations = inputs = paddles = vehicleMoves = 0;
user.delayedWrite(new KeepAlivePacket(expectedKeepAliveId));
user.channel().flush();
}
// Bedrock users do not send SetPlayerPositionRotation and SetPlayerRotation packets
// Don't ask me why; Microsoft is doing some *fascinating* things with Bedrock...
if (user.isGeyser()) {
handleRotation();
} else {
checkState(rotationPackets >= inputPackets,
"illegal packet order; r/i " + rotationPackets + "/" + inputPackets);
}
private void markSuccess() {
// Pass the player to the next best verification handler
if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackCaptchaHandler(user));
} else {
finishVerification();
}
}
// 1.8 and below do not have PaddleBoat packets, so we simply exempt them from the PaddleBoat check.
// Clients don't send PaddleBoat & VehicleMovePacket packets while riding minecarts.
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0 || inMinecart) {
paddlePackets++;
vehicleMovePackets++;
} else if (!user.isGeyser()) {
checkState(paddlePackets >= inputPackets,
"illegal packet order; i/p " + inputPackets + "/" + paddlePackets);
checkState(vehicleMovePackets >= inputPackets,
"illegal packet order; i/v " + inputPackets + "/" + vehicleMovePackets);
}
inputPackets++;
}
private void handleMovement(final double y, final boolean isOnGround) {
// Make sure we're currently expecting movement
if (state.inVehicle || state == State.WAITING) {
return;
}
// Make sure the ground state and y position are correct
checkState(y <= boatY, "invalid y: " + y);
checkState(!isOnGround, "invalid ground state: " + y);
if (state == State.IN_AIR_AFTER_BOAT) {
spawnVehicle(State.IN_MINECART);
} else {
markSuccess();
}
}
private void handlePlayerInput() {
// 1.8 and below do not have PaddleBoat packets, so we simply exempt them from the PaddleBoat check.
// Clients also don't send PaddleBoat & VehicleMovePacket packets while riding minecarts.
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_9) || state == State.IN_MINECART) {
paddles++;
vehicleMoves++;
}
// Check for packet order
checkState(rotations >= inputs,
"illegal packet order; i/r " + inputs + "/" + rotations);
checkState(paddles >= inputs,
"illegal packet order; i/p " + inputs + "/" + paddles);
checkState(vehicleMoves >= inputs,
"illegal packet order; i/v " + inputs + "/" + vehicleMoves);
inputs++;
// Check if we've received more than the minimum number of packets
final int minimumPackets = Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets();
if (inputs > minimumPackets && rotations > minimumPackets
&& paddles > minimumPackets && vehicleMoves > minimumPackets) {
// Move on to the next stage
user.delayedWrite(REMOVE_VEHICLE);
prepareForNextState(state == State.IN_BOAT ? State.IN_AIR_AFTER_BOAT : State.IN_AIR_AFTER_MINECART);
}
}
}
@@ -25,19 +25,19 @@ import xyz.jonesdev.sonar.api.event.impl.UserBlacklistedEvent;
import xyz.jonesdev.sonar.api.event.impl.UserVerifyFailedEvent;
import xyz.jonesdev.sonar.api.event.impl.UserVerifySuccessEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarIntLengthEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketListener;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.util.Random;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_5;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.transferToOrigin;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.closeWith;
@RequiredArgsConstructor
public abstract class FallbackVerificationHandler implements FallbackPacketListener {
@@ -53,22 +53,21 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
user.getFingerprint(), user.getLoginTimer().getStart()));
// Call the VerifySuccessEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(user, user.getLoginTimer()));
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(user));
// If enabled, transfer the player back to the origin server.
// This feature was introduced by Mojang in Minecraft version 1.20.5.
if (transferToOrigin != null
&& user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
if (transferToOrigin != null && user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
// Send the transfer packet to the player (and close the channel if on Java Edition)
if (user.isGeyser()) {
user.write(transferToOrigin);
// Make sure we cannot receive any more packets from the player
user.getPipeline().remove(FallbackPacketDecoder.class);
user.getPipeline().remove(FallbackPacketEncoder.class);
user.getPipeline().remove(FallbackVarInt21FrameDecoder.class);
user.getPipeline().remove(FallbackVarIntLengthEncoder.class);
user.channel().pipeline().remove(FallbackPacketDecoder.class);
user.channel().pipeline().remove(FallbackPacketEncoder.class);
user.channel().pipeline().remove(FallbackVarInt21FrameDecoder.class);
user.channel().pipeline().remove(FallbackVarIntLengthEncoder.class);
} else {
closeWith(user.getChannel(), user.getProtocolVersion(), transferToOrigin);
ProtocolUtil.closeWith(user.channel(), user.getProtocolVersion(), transferToOrigin);
}
} else {
// Disconnect player with the verification success message
@@ -130,7 +130,7 @@ public final class GlobalSonarStatistics implements SonarStatistics {
@Override
public int getTotalPlayersVerified() {
return Sonar.get().getVerifiedPlayerController().estimatedSize();
return Sonar.get().getVerifiedPlayerController().getCache().size();
}
@Override
@@ -21,7 +21,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
@@ -32,20 +32,20 @@ import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
public final class BlacklistCommand extends Subcommand {
@Override
protected void execute(final @NotNull CommandInvocation invocation) {
switch (invocation.getRawArguments()[1].toLowerCase()) {
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
switch (args[1].toLowerCase()) {
case "add": {
if (invocation.getRawArguments().length <= 2) {
incorrectUsage(invocation.getSource(), "blacklist add <IP address>");
if (args.length <= 2) {
incorrectUsage(source, "blacklist add <IP address>");
return;
}
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
final String rawAddress = validateIP(source, args[2]);
// Make sure the given IP address is valid
if (rawAddress == null) return;
if (Sonar.get().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-duplicate"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("ip", rawAddress)));
@@ -53,7 +53,7 @@ public final class BlacklistCommand extends Subcommand {
}
Sonar.get().getFallback().getBlacklist().put(rawAddress, 1337 /* arbitrarily high number */);
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("ip", rawAddress)));
@@ -61,24 +61,24 @@ public final class BlacklistCommand extends Subcommand {
}
case "remove": {
if (invocation.getRawArguments().length <= 2) {
incorrectUsage(invocation.getSource(), "blacklist remove <IP address>");
if (args.length <= 2) {
incorrectUsage(source, "blacklist remove <IP address>");
return;
}
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
final String rawAddress = validateIP(source, args[2]);
// Make sure the given IP address is valid
if (rawAddress == null) return;
if (!Sonar.get().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-not-found"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
return;
}
Sonar.get().getFallback().getBlacklist().invalidate(rawAddress);
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.remove"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("ip", rawAddress)));
@@ -89,14 +89,14 @@ public final class BlacklistCommand extends Subcommand {
final long blacklistSize = Sonar.get().getFallback().getBlacklist().estimatedSize();
if (blacklistSize == 0) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.empty"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
return;
}
Sonar.get().getFallback().getBlacklist().invalidateAll();
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.clear"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(blacklistSize))));
@@ -106,7 +106,7 @@ public final class BlacklistCommand extends Subcommand {
case "size": {
final long blacklistSize = Sonar.get().getFallback().getBlacklist().estimatedSize();
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.size"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(blacklistSize))));
@@ -114,7 +114,7 @@ public final class BlacklistCommand extends Subcommand {
}
default: {
incorrectUsage(invocation.getSource());
incorrectUsage(source);
break;
}
}
@@ -23,7 +23,7 @@ import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
@@ -46,7 +46,7 @@ public final class DumpCommand extends Subcommand {
private static final Gson GSON = new GsonBuilder().create();
@Override
protected void execute(final @NotNull CommandInvocation invocation) {
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
final var mappings = new WeakHashMap<String, Object>();
mappings.put("sonar", new Dump.Sonar(
Sonar.get().getVersion().getFormatted(),
@@ -21,7 +21,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
@@ -32,17 +32,17 @@ import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
public final class NotifyCommand extends Subcommand {
@Override
protected void execute(final @NotNull CommandInvocation invocation) {
if (Sonar.get().getChatNotificationHandler().isSubscribed(invocation.getSource().getUuid())) {
Sonar.get().getChatNotificationHandler().unsubscribe(invocation.getSource().getUuid());
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
if (Sonar.get().getChatNotificationHandler().isSubscribed(source.getUuid())) {
Sonar.get().getChatNotificationHandler().unsubscribe(source.getUuid());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.unsubscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
return;
}
Sonar.get().getChatNotificationHandler().subscribe(invocation.getSource().getUuid());
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getChatNotificationHandler().subscribe(source.getUuid());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.subscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
}
@@ -21,7 +21,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
@@ -32,8 +32,8 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer;
public final class ReloadCommand extends Subcommand {
@Override
protected void execute(final @NotNull CommandInvocation invocation) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.reload.start"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
@@ -41,7 +41,7 @@ public final class ReloadCommand extends Subcommand {
Sonar.get().reload();
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.reload.finish"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("time-taken", String.valueOf(timer.delay()))));
@@ -23,11 +23,12 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
import java.util.List;
import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
@SubcommandInfo(
@@ -39,13 +40,13 @@ import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
public final class StatisticsCommand extends Subcommand {
@Override
protected void execute(final @NotNull CommandInvocation invocation) {
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
String type = "general";
if (invocation.getRawArguments().length >= 2) {
if (args.length >= 2) {
try {
type = invocation.getRawArguments()[1].toLowerCase();
type = args[1].toLowerCase();
} catch (Exception exception) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.statistics.unknown-type"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("statistics", getArguments())));
@@ -53,11 +54,11 @@ public final class StatisticsCommand extends Subcommand {
}
}
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.statistics.header"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("statistics-type", type)));
invocation.getSource().sendMessage(Component.empty());
source.sendMessage(Component.empty());
TagResolver.@NotNull Single[] placeholders = null;
@@ -67,17 +68,18 @@ public final class StatisticsCommand extends Subcommand {
final long days = seconds / (24L * 60L * 60L);
final long hours = (seconds % (24L * 60L * 60L)) / (60L * 60L);
final long minutes = (seconds % (60L * 60L)) / 60L;
final String serverUptime = String.format("%dd %dh %dm %ds", days, hours, minutes, seconds % 60L);
placeholders = new TagResolver.Single[]{
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("verified", DECIMAL_FORMAT.format(Sonar.get().getVerifiedPlayerController().estimatedSize())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("server-uptime", String.format("%dd %dh %dm %ds", days, hours, minutes, seconds % 60L)),
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("total-attempts", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("total-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications()))
Placeholder.unparsed("verified", Sonar.DECIMAL_FORMAT.format(Sonar.get().getVerifiedPlayerController().getCache().size())),
Placeholder.unparsed("verifying", Sonar.DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", Sonar.DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("queued", Sonar.DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("server-uptime", serverUptime),
Placeholder.unparsed("total-joins", Sonar.DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("total-attempts", Sonar.DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("total-failed", Sonar.DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications()))
};
break;
}
@@ -85,11 +87,11 @@ public final class StatisticsCommand extends Subcommand {
case "cpu": {
placeholders = new TagResolver.Single[]{
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("process-cpu", DECIMAL_FORMAT.format(getProcessCPUUsage())),
Placeholder.unparsed("system-cpu", DECIMAL_FORMAT.format(getSystemCPUUsage())),
Placeholder.unparsed("average-process-cpu", DECIMAL_FORMAT.format(getAverageProcessCPUUsage())),
Placeholder.unparsed("average-system-cpu", DECIMAL_FORMAT.format(getAverageSystemCPUUsage())),
Placeholder.unparsed("virtual-core-count", DECIMAL_FORMAT.format(getVirtualCores()))
Placeholder.unparsed("process-cpu", Sonar.DECIMAL_FORMAT.format(getProcessCPUUsage())),
Placeholder.unparsed("system-cpu", Sonar.DECIMAL_FORMAT.format(getSystemCPUUsage())),
Placeholder.unparsed("average-process-cpu", Sonar.DECIMAL_FORMAT.format(getAverageProcessCPUUsage())),
Placeholder.unparsed("average-system-cpu", Sonar.DECIMAL_FORMAT.format(getAverageSystemCPUUsage())),
Placeholder.unparsed("virtual-core-count", Sonar.DECIMAL_FORMAT.format(getVirtualCores()))
};
break;
}
@@ -117,8 +119,10 @@ public final class StatisticsCommand extends Subcommand {
}
}
for (final String msg : Sonar.get().getConfig().getMessagesConfig().getStringList("commands.statistics." + type)) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(msg, placeholders));
final List<String> parts = Sonar.get().getConfig().getMessagesConfig().getStringList(
"commands.statistics." + type);
for (final String msg : parts) {
source.sendMessage(MiniMessage.miniMessage().deserialize(msg, placeholders));
}
}
}
@@ -22,7 +22,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
@@ -33,19 +33,19 @@ import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
public final class VerboseCommand extends Subcommand {
@Override
protected void execute(final @NotNull CommandInvocation invocation) {
if (Sonar.get().getActionBarNotificationHandler().isSubscribed(invocation.getSource().getUuid())) {
Sonar.get().getActionBarNotificationHandler().unsubscribe(invocation.getSource().getUuid());
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
if (Sonar.get().getActionBarNotificationHandler().isSubscribed(source.getUuid())) {
Sonar.get().getActionBarNotificationHandler().unsubscribe(source.getUuid());
// Reset ActionBar component when unsubscribing
invocation.getSource().getAudience().sendActionBar(Component.empty());
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.getAudience().sendActionBar(Component.empty());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verbose.unsubscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
return;
}
Sonar.get().getActionBarNotificationHandler().subscribe(invocation.getSource().getUuid());
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getActionBarNotificationHandler().subscribe(source.getUuid());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verbose.subscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
}
@@ -21,7 +21,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
import xyz.jonesdev.sonar.api.database.model.VerifiedPlayer;
@@ -34,30 +34,30 @@ import xyz.jonesdev.sonar.api.fingerprint.FingerprintingUtil;
public final class VerifiedCommand extends Subcommand {
@Override
protected void execute(final @NotNull CommandInvocation invocation) {
switch (invocation.getRawArguments()[1].toLowerCase()) {
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
switch (args[1].toLowerCase()) {
case "remove": {
if (invocation.getRawArguments().length <= 3) {
incorrectUsage(invocation.getSource(), "verified remove <IP address> <username>");
if (args.length <= 3) {
incorrectUsage(source, "verified remove <IP address> <username>");
return;
}
final String hostAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
final String hostAddress = validateIP(source, args[2]);
// Make sure the given IP address is valid
if (hostAddress == null) return;
final String username = invocation.getRawArguments()[3];
final String username = args[3];
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (!Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.ip-not-found"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
return;
}
Sonar.get().getVerifiedPlayerController().remove(fingerprint);
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.remove"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("ip", hostAddress),
@@ -66,27 +66,27 @@ public final class VerifiedCommand extends Subcommand {
}
case "add": {
if (invocation.getRawArguments().length <= 3) {
incorrectUsage(invocation.getSource(), "verified add <IP address> <username>");
if (args.length <= 3) {
incorrectUsage(source, "verified add <IP address> <username>");
return;
}
final String hostAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
final String hostAddress = validateIP(source, args[2]);
// Make sure the given IP address is valid
if (hostAddress == null) return;
final String username = invocation.getRawArguments()[3];
final String username = args[3];
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.already"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
return;
}
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(fingerprint, System.currentTimeMillis()));
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.add"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("ip", hostAddress),
@@ -95,17 +95,17 @@ public final class VerifiedCommand extends Subcommand {
}
case "clear": {
final int verifiedSize = Sonar.get().getVerifiedPlayerController().estimatedSize();
final int verifiedSize = Sonar.get().getVerifiedPlayerController().getCache().size();
if (verifiedSize == 0) {
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.empty"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
return;
}
Sonar.get().getVerifiedPlayerController().clearAll();
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.clear"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(verifiedSize))));
@@ -113,9 +113,9 @@ public final class VerifiedCommand extends Subcommand {
}
case "size": {
final int verifiedSize = Sonar.get().getVerifiedPlayerController().estimatedSize();
final int verifiedSize = Sonar.get().getVerifiedPlayerController().getCache().size();
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.size"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(verifiedSize))));
@@ -123,7 +123,7 @@ public final class VerifiedCommand extends Subcommand {
}
default: {
incorrectUsage(invocation.getSource());
incorrectUsage(source);
break;
}
}
@@ -22,7 +22,6 @@ import com.google.gson.internal.LazilyParsedNumber;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
@@ -31,20 +30,19 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_3;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeNamelessCompoundTag;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeString;
public final class ComponentHolder {
private final String serializedComponent;
private BinaryTag cachedBinaryTag;
private final String modernJson;
private final String legacyJson;
private final BinaryTag binaryTag;
public ComponentHolder(final Component component) {
this.serializedComponent = GsonComponentSerializer.gson().serialize(component);
public ComponentHolder(final @NotNull Component component) {
this.modernJson = ProtocolUtil.PRE_1_20_3_SERIALIZER.serialize(component);
this.legacyJson = ProtocolUtil.PRE_1_16_SERIALIZER.serialize(component);
this.binaryTag = serialize(new JsonParser().parse(ProtocolUtil.MODERN_SERIALIZER.serialize(component)));
}
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java
private BinaryTag serialize(final JsonElement json) {
private BinaryTag serialize(final @NotNull JsonElement json) {
if (json instanceof JsonPrimitive) {
final JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
@@ -141,15 +139,12 @@ public final class ComponentHolder {
return EndBinaryTag.endBinaryTag();
}
public void write(final @NotNull ByteBuf byteBuf,
final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.compareTo(MINECRAFT_1_20_3) >= 0) {
if (cachedBinaryTag == null) {
cachedBinaryTag = serialize(new JsonParser().parse(serializedComponent));
}
writeNamelessCompoundTag(byteBuf, cachedBinaryTag);
public void write(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion, final boolean forceJson) {
if (!forceJson && protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_3)) {
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, binaryTag);
} else {
writeString(byteBuf, serializedComponent);
ProtocolUtil.writeString(byteBuf,
protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16) ? modernJson : legacyJson);
}
}
}
@@ -47,26 +47,61 @@ import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import io.netty.util.Version;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.nbt.*;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagType;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.json.JSONOptions;
import net.kyori.option.OptionState;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_7_2;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
@UtilityClass
public class ProtocolUtil {
public static final GsonComponentSerializer PRE_1_16_SERIALIZER = GsonComponentSerializer.builder()
.downsampleColors()
.options(OptionState.optionState()
// before 1.16
.value(JSONOptions.EMIT_RGB, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.LEGACY_ONLY)
// before 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
.build())
.build();
public static final GsonComponentSerializer PRE_1_20_3_SERIALIZER = GsonComponentSerializer.builder()
.options(OptionState.optionState()
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY)
// before 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
.build())
.build();
public static final GsonComponentSerializer MODERN_SERIALIZER = GsonComponentSerializer.builder()
.options(OptionState.optionState()
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY)
// after 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
.build())
.build();
public static final boolean DEBUG = Boolean.getBoolean("sonar.debug-traces");
public static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
public static final String BRAND_CHANNEL = "minecraft:brand";
private static final int[] VAR_INT_LENGTHS = new int[65];
static {
@@ -105,7 +140,7 @@ public class ProtocolUtil {
throw DEBUG ? new DecoderException("Bad VarInt") : QuietDecoderException.INSTANCE;
}
public static void writeVarInt(final ByteBuf byteBuf, final int value) {
public static void writeVarInt(final @NotNull ByteBuf byteBuf, final int value) {
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
// that the proxy will write, to improve inlining.
if ((value & (0xFFFFFFFF << 7)) == 0) {
@@ -118,7 +153,7 @@ public class ProtocolUtil {
}
}
private void writeVarIntFull(final ByteBuf byteBuf, final int value) {
private void writeVarIntFull(final @NotNull ByteBuf byteBuf, final int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
if ((value & (0xFFFFFFFF << 7)) == 0) {
byteBuf.writeByte(value);
@@ -140,7 +175,7 @@ public class ProtocolUtil {
}
}
public static void writeVarLong(final ByteBuf byteBuf, final long value) {
public static void writeVarLong(final @NotNull ByteBuf byteBuf, final long value) {
// Peel the one and two byte count cases explicitly as they are the most common VarLong sizes
// that the proxy will write, to improve inlining.
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
@@ -153,7 +188,7 @@ public class ProtocolUtil {
}
}
private void writeVarLongFull(final ByteBuf byteBuf, final long value) {
private void writeVarLongFull(final @NotNull ByteBuf byteBuf, final long value) {
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
byteBuf.writeByte((byte) value);
} else if ((value & 0xFFFFFFFFFFFFC000L) == 0L) {
@@ -225,8 +260,7 @@ public class ProtocolUtil {
public static void closeWith(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull Object msg) {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0
&& protocolVersion.compareTo(MINECRAFT_1_7_2) >= 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
channel.eventLoop().execute(() -> {
channel.config().setAutoRead(false);
channel.eventLoop().schedule(() -> {
@@ -242,11 +276,11 @@ public class ProtocolUtil {
return new UUID(byteBuf.readLong(), byteBuf.readLong());
}
public static byte @NotNull [] readByteArray(final ByteBuf byteBuf) {
public static byte @NotNull [] readByteArray(final @NotNull ByteBuf byteBuf) {
return readByteArray(byteBuf, Short.MAX_VALUE);
}
public static byte @NotNull [] readByteArray(final ByteBuf byteBuf, final int cap) {
public static byte @NotNull [] readByteArray(final @NotNull ByteBuf byteBuf, final int cap) {
final int length = readVarInt(byteBuf);
checkState(length >= 0, "Got a negative-length array");
checkState(length <= cap, "Bad array size");
@@ -256,7 +290,7 @@ public class ProtocolUtil {
return array;
}
public static @NotNull String readString(final ByteBuf byteBuf, final int cap) throws DecoderException {
public static @NotNull String readString(final @NotNull ByteBuf byteBuf, final int cap) throws DecoderException {
final int length = readVarInt(byteBuf);
return readString(byteBuf, cap, length);
}
@@ -273,7 +307,7 @@ public class ProtocolUtil {
return str;
}
public static void writeString(final ByteBuf byteBuf, final @NotNull CharSequence str) {
public static void writeString(final @NotNull ByteBuf byteBuf, final @NotNull CharSequence str) {
final int size = ByteBufUtil.utf8Bytes(str);
writeVarInt(byteBuf, size);
byteBuf.writeCharSequence(str, StandardCharsets.UTF_8);
@@ -284,69 +318,33 @@ public class ProtocolUtil {
byteBuf.writeLong(uuid.getLeastSignificantBits());
}
public static void writeUUIDIntArray(final @NotNull ByteBuf byteBuf, final @NotNull UUID uuid) {
byteBuf.writeInt((int) (uuid.getMostSignificantBits() >> 32));
byteBuf.writeInt((int) uuid.getMostSignificantBits());
byteBuf.writeInt((int) (uuid.getLeastSignificantBits() >> 32));
byteBuf.writeInt((int) uuid.getLeastSignificantBits());
}
public static void writeByteArray(final ByteBuf byteBuf, final byte @NotNull [] bytes) {
checkState(bytes.length < Short.MAX_VALUE, "Too long array");
public static void writeByteArray(final @NotNull ByteBuf byteBuf, final byte @NotNull [] bytes) {
writeVarInt(byteBuf, bytes.length);
byteBuf.writeBytes(bytes);
}
public static void writeStringArray(final ByteBuf byteBuf, final String @NotNull [] stringArray) {
public static void writeStringArray(final @NotNull ByteBuf byteBuf, final String @NotNull [] stringArray) {
writeVarInt(byteBuf, stringArray.length);
for (final String s : stringArray) {
writeString(byteBuf, s);
}
}
public static void writeCompoundTag(final @NotNull ByteBuf byteBuf, final @NotNull CompoundBinaryTag compoundTag) {
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java#L458
@SuppressWarnings("unchecked")
public static <T extends BinaryTag> void writeBinaryTag(final @NotNull ByteBuf byteBuf,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull T tag) {
final BinaryTagType<T> type = (BinaryTagType<T>) tag.type();
byteBuf.writeByte(type.id());
try {
BinaryTagIO.writer().write(compoundTag, (DataOutput) new ByteBufOutputStream(byteBuf));
} catch (IOException exception) {
throw new EncoderException("Unable to encode NBT CompoundTag");
}
}
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/ByteMessage.java#L219
public static void writeNamelessCompoundTag(final @NotNull ByteBuf byteBuf, final @NotNull BinaryTag binaryTag) {
try (final ByteBufOutputStream output = new ByteBufOutputStream(byteBuf)) {
// TODO: Find a way to improve this...
output.writeByte(binaryTag.type().id());
if (binaryTag instanceof CompoundBinaryTag) {
CompoundBinaryTag tag = (CompoundBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof ByteBinaryTag) {
ByteBinaryTag tag = (ByteBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof ShortBinaryTag) {
ShortBinaryTag tag = (ShortBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof IntBinaryTag) {
IntBinaryTag tag = (IntBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof LongBinaryTag) {
LongBinaryTag tag = (LongBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof DoubleBinaryTag) {
DoubleBinaryTag tag = (DoubleBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof StringBinaryTag) {
StringBinaryTag tag = (StringBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof ListBinaryTag) {
ListBinaryTag tag = (ListBinaryTag) binaryTag;
tag.type().write(tag, output);
} else if (binaryTag instanceof EndBinaryTag) {
EndBinaryTag tag = (EndBinaryTag) binaryTag;
tag.type().write(tag, output);
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
// pre-1.20.2 clients need an empty name
byteBuf.writeShort(0);
}
type.write(tag, new ByteBufOutputStream(byteBuf));
} catch (IOException exception) {
throw new EncoderException("Unable to encode NBT CompoundTag");
throw new EncoderException("Unable to encode BinaryTag", exception);
}
}
@@ -360,6 +358,25 @@ public class ProtocolUtil {
return ((high & 0xFF) << 15) | low;
}
public static CompoundBinaryTag getCodec(final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21)) {
return DimensionRegistry.CODEC_1_21;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20)) {
return DimensionRegistry.CODEC_1_20;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_4)) {
return DimensionRegistry.CODEC_1_19_4;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_1)) {
return DimensionRegistry.CODEC_1_19_1;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
return DimensionRegistry.CODEC_1_19;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_18_2)) {
return DimensionRegistry.CODEC_1_18_2;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16_2)) {
return DimensionRegistry.CODEC_1_16_2;
}
return DimensionRegistry.CODEC_1_16;
}
private void checkState(final boolean expression, final String message) {
if (!expression) {
throw DEBUG ? new DecoderException(message) : QuietDecoderException.INSTANCE;
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.exception;
package xyz.jonesdev.sonar.common.util.exception;
import xyz.jonesdev.sonar.api.Sonar;
@@ -165,6 +165,12 @@ verification:
# - CREATIVE: životy a hunger neni zobrazen
# - ADVENTURE: všechny komponenty UI jsou zobrazeny
gamemode: ADVENTURE
# Letní čas v ověřovacím světě
# Všechny letní časy najdete zde:
# https://minecraft.wiki/w/Daylight_cycle
# Jedná se pouze o vizuální nastavení, které nemění proces ověřování!
# Pokud je tato hodnota nastavena na 1000, paket nebude odeslán.
time-of-day: 1000
# Sonar ukládá do mezipaměti většinu paketů, které odesílá klientovi, včetně paketu LoginSuccess.
# Které uživatelské jméno má Sonar použít pro paket LoginSuccess?
# Uživatelské jméno nebude pro hráče viditelné bez úpravy klienta
@@ -188,7 +194,7 @@ verification:
write-timeout: 10000
# Jak dlouho by měl hráč čekat, než se při ověřování znovu připojí
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
rejoin-delay: 8000
rejoin-delay: 5000
# Jak dlouho by si měl Sonar pamatovat počet neúspěšných ověření hráče?
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
remember-time: 120000
@@ -162,6 +162,12 @@ verification:
# - CREATIVE: Gesundheit und Hunger sind versteckt
# - ADVENTURE: alle UI-Komponenten sind sichtbar
gamemode: ADVENTURE
# Tageszeit in der Verifizierungswelt
# Alle Tageszeiten finden Sie hier:
# https://minecraft.wiki/w/Daylight_cycle
# Dies ist nur eine visuelle Einstellung und ändert nichts am Verifizierungsprozess!
# Wenn dieser Wert auf 1000 gesetzt ist, wird das Paket nicht gesendet.
time-of-day: 1000
# Sonar speichert die meisten Pakete, die es an den Client sendet, einschließlich des LoginSuccess-Pakets.
# Welchen Benutzernamen soll Sonar für das LoginSuccess-Paket verwenden?
# Der Benutzername ist für den Spieler ohne Änderungen am Client nicht sichtbar
@@ -185,7 +191,7 @@ verification:
write-timeout: 10000
# Wie lange sollte ein Spieler warten, bevor er während der Überprüfung erneut verbinden kann
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
rejoin-delay: 8000
rejoin-delay: 5000
# How long should Sonar remember the amount of failed verifications for a player?
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
remember-time: 120000
@@ -165,6 +165,12 @@ verification:
# - CREATIVE: health and hunger are hidden
# - ADVENTURE: all UI components are visible
gamemode: ADVENTURE
# Daylight time in the verification world
# You can find all daylight times here:
# https://minecraft.wiki/w/Daylight_cycle
# This is only a visual setting and does not change the verification process!
# If this value is set to 1000, the packet will not be sent.
time-of-day: 1000
# Sonar caches most packets it sends to the client, including the LoginSuccess packet.
# Which username should Sonar use for the LoginSuccess packet?
# The username will not be visible to the player without client modifications
@@ -188,7 +194,7 @@ verification:
write-timeout: 10000
# How long should a player wait before reconnecting during verification
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
rejoin-delay: 8000
rejoin-delay: 5000
# How long should Sonar remember the number of failed verifications for a player?
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
remember-time: 120000
@@ -164,6 +164,12 @@ verification:
# - CREATIVE : la santé et la faim sont cachées
# - ADVENTURE : tous les composants de l'interface utilisateur sont visibles
gamemode: ADVENTURE
# L'heure d'été dans le monde de la vérification
# Vous pouvez trouver toutes les heures d'été ici :
# https://minecraft.wiki/w/Daylight_cycle
# Il s'agit uniquement d'un paramètre visuel qui ne modifie pas le processus de vérification !
# Si cette valeur est fixée à 1000, le paquet ne sera pas envoyé.
time-of-day: 1000
# Sonar met en cache la plupart des paquets qu'il envoie au client, y compris le paquet LoginSuccess.
# Quel nom d'utilisateur Sonar doit-il utiliser pour le paquet LoginSuccess ?
# Le nom d'utilisateur ne sera pas visible par le joueur sans modification du client
@@ -187,7 +193,7 @@ verification:
write-timeout: 10000
# Combien de temps un joueur doit-il attendre avant de se reconnecter pendant la vérification
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
rejoin-delay: 8000
rejoin-delay: 5000
# Combien de temps Sonar doit-il se souvenir du nombre de vérifications échouées pour un joueur ?
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
remember-time: 120000
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more