Compare commits

..
57 Commits
Author SHA1 Message Date
Jones 43a1aa2ae8 Release 2.1.16 (#409) 2024-11-11 14:14:04 +01:00
Jones f36a613124 Clear ByteBuf if the packet has no start 2024-11-08 15:53:39 +01:00
Jones 3e193bddd7 Fix "invalid username" never being used 2024-11-07 18:44:43 +01:00
Jones 66d0651d1e Release 2.1.15 (#407) 2024-11-04 15:35:41 +01:00
dependabot[bot] b21c4e8a9a Bump com.gradleup.shadow from 8.3.3 to 8.3.5 (#406)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.3 to 8.3.5.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.3...8.3.5)

---
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-11-04 15:23:40 +01:00
Jones b77e0c4806 Remove accidental debugging statement 2024-11-03 12:36:04 +01:00
Jones cb76dfe06e Release 2.1.14 (#405) 2024-11-02 14:59:53 +01:00
Jones d0a758473f Move username check to post-login 2024-11-02 14:57:11 +01:00
Jones e4dfab87da Fix pre-join disconnects not encoding correctly 2024-11-02 14:53:57 +01:00
Jones 2fe3ab1d8d Make unsupported version message configurable 2024-11-02 14:53:27 +01:00
Akkariin 4483e52852 Update language/config files for chinese. (#404)
* Update language/config files for chinese.

* Emphasizing parts makes it an accurate description of something.

* [ci skip] Fix space
2024-10-31 22:01:24 +01:00
Jones 22b41151d6 [ci skip] Display sponsors in README 2024-10-30 18:12:58 +01:00
Jones e849060db1 [ci skip] Add FUNDING.yml 2024-10-28 17:11:23 +01:00
Jones 59b338e775 Add client ticking check for 1.21.2+ 2024-10-27 15:50:44 +01:00
Jones e33ee1333a Release 2.1.13 (#403) 2024-10-26 17:10:37 +02:00
Jones 50e6b017f8 Don't serialize modern json twice 2024-10-26 17:08:46 +02:00
Jones f25cc18cb9 Fix NoClassDefFoundError on BungeeCord and Bukkit 2024-10-26 17:08:29 +02:00
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
115 changed files with 1783 additions and 1645 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
patreon: jonesdev
+5
View File
@@ -48,6 +48,11 @@ Sonar also protects against spambot attacks since it queues the incoming connect
Sonar is unlikely to ever falsely prevent a player from joining the server since Minecraft uses the TCP protocol, which means that packets are always sent in the correct order. Therefore, lag or ping should not affect the bot check.
If you or one of your players experiences a false positive, make sure to report it by opening a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues/new/choose) or a ticket on the [Discord server](https://jonesdev.xyz/discord/).
## Sponsors
Massive thanks to the sponsors of Sonar who help keep this project running:
<a href="https://github.com/Hydoxl"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/107579333?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
## Building
If you want to build your own version of Sonar, please take a look at the [Sonar building documentation](https://docs.jonesdev.xyz/development/building).
<br>
@@ -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();
@@ -259,6 +266,7 @@ public final class SonarConfiguration {
tooManyOnlinePerIp = deserializeDisconnectMessage("too-many-online-per-ip");
verification.currentlyPreparing = deserializeDisconnectMessage("verification.currently-preparing");
verification.unsupportedVersion = deserializeDisconnectMessage("verification.unsupported-version");
verification.tooFastReconnect = deserializeDisconnectMessage("verification.too-fast-reconnect");
verification.alreadyVerifying = deserializeDisconnectMessage("verification.already-verifying");
verification.alreadyQueued = deserializeDisconnectMessage("verification.already-queued");
@@ -408,6 +416,7 @@ public final class SonarConfiguration {
}
}
private int timeOfDay;
private boolean checkGeyser;
private boolean logConnections;
private boolean logDuringAttack;
@@ -432,6 +441,7 @@ public final class SonarConfiguration {
private Component blacklisted;
private Component protocolBlacklisted;
private Component currentlyPreparing;
private Component unsupportedVersion;
}
@Getter
@@ -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;
}
@@ -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,6 +28,7 @@ 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;
@@ -35,8 +36,6 @@ import java.util.Objects;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INBOUND_HANDLER;
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;
@@ -79,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
@@ -93,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;
}
/*
@@ -122,7 +122,8 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
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;
@@ -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();
}
}
@@ -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
@@ -83,8 +83,7 @@ public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
* will run before the connection knows that there has been an error.
*/
@Override
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx,
final @NotNull Throwable cause) throws Exception {
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx, final Throwable cause) throws Exception {
ctx.close();
}
}
@@ -25,10 +25,13 @@ 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.fallback.protocol.FallbackPacketRegistry;
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 +41,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
@@ -72,17 +74,23 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
final @NotNull Runnable initialLoginAction,
final @NotNull String username,
final @NotNull InetSocketAddress socketAddress) throws Exception {
// Check if the player has already sent a login packet
if (this.username != null) {
// Count every single attempted login
GlobalSonarStatistics.countLogin();
// Ensure that the player sent a handshake packet
if (protocolVersion == null) {
throw QuietDecoderException.INSTANCE;
}
// Connections from unknown protocol versions will be discarded
// 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()) {
customDisconnect(ctx.channel(), unsupportedVersion, ProtocolVersion.MINECRAFT_1_7_2);
return;
}
// Ensure that the player has not sent a login packet yet
if (this.username != null) {
throw QuietDecoderException.INSTANCE;
}
GlobalSonarStatistics.countLogin();
this.username = username;
final InetAddress inetAddress = socketAddress.getAddress();
@@ -149,17 +157,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
return null;
}
return () -> {
// 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(ctx.channel(), invalidUsername, protocolVersion);
return;
}
// Create an instance for the user and let the verification handler take over the channel
new FallbackUserWrapper(ctx.channel(), inetAddress, protocolVersion, username, fingerprint, geyser);
};
// Create an instance for the user and let the verification handler take over the channel
return () -> new FallbackUserWrapper(ctx, inetAddress, protocolVersion, username, fingerprint, geyser);
});
}
@@ -214,7 +213,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
private static void customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final ProtocolVersion protocolVersion) {
final @NotNull ProtocolVersion protocolVersion) {
if (channel.isActive()) {
if (channel.eventLoop().inEventLoop()) {
_customDisconnect(channel, packet, protocolVersion);
@@ -229,7 +228,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
*/
private static void _customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final ProtocolVersion protocolVersion) {
final @NotNull ProtocolVersion protocolVersion) {
// TODO: recode this
// Remove the connection handler pipeline to completely take over the channel
final String handler = Sonar.get().getPlatform().getConnectionHandler();
if (channel.pipeline().context(handler) != null) {
@@ -242,9 +242,10 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
// We don't need to update the encoder if it has already been replaced by Sonar
if (!(currentEncoder instanceof FallbackPacketEncoder)) {
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
newEncoder.updateRegistry(FallbackPacketRegistry.LOGIN);
channel.pipeline().replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
}
closeWith(channel, protocolVersion, packet);
ProtocolUtil.closeWith(channel, protocolVersion, packet);
} else {
channel.close();
}
@@ -18,6 +18,7 @@
package xyz.jonesdev.sonar.common.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@@ -34,18 +35,16 @@ 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"})
@@ -61,13 +60,13 @@ public final class FallbackUserWrapper implements FallbackUser {
@Setter
private boolean forceCaptcha;
public FallbackUserWrapper(final @NotNull Channel channel,
public FallbackUserWrapper(final @NotNull ChannelHandlerContext ctx,
final @NotNull InetAddress inetAddress,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull String username,
final @NotNull String fingerprint,
final boolean geyser) {
this.channel = channel;
this.channel = ctx.channel();
this.inetAddress = inetAddress;
this.protocolVersion = protocolVersion;
this.username = username;
@@ -88,7 +87,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(() -> {
@@ -101,24 +100,34 @@ 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);
channel.pipeline().addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
channel.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);
// Replace normal decoder to allow custom packets
// TODO: recode injection
final FallbackPacketDecoder newDecoder = new FallbackPacketDecoder(protocolVersion);
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
// TODO: recode injection
channel.pipeline().addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
});
}
@@ -126,7 +135,7 @@ public final class FallbackUserWrapper implements FallbackUser {
@Override
public void disconnect(final @NotNull Component reason) {
final FallbackPacketEncoder encoder = channel.pipeline().get(FallbackPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() != GAME;
closeWith(channel, protocolVersion, DisconnectPacket.create(reason, duringLogin));
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() == FallbackPacketRegistry.LOGIN;
ProtocolUtil.closeWith(channel, protocolVersion, new DisconnectPacket(reason, duringLogin));
}
}
@@ -30,8 +30,7 @@ public final class FallbackTailExceptionsHandler extends ChannelDuplexHandler {
// does not have any other pipelines that could handle this error.
// Additionally, this will also run after our custom decoder.
@Override
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx,
final @NotNull Throwable cause) throws Exception {
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx, 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,8 +41,9 @@ 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) {
byteBuf.clear();
return;
}
byteBuf.readerIndex(packetStart);
@@ -56,7 +56,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 +75,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,8 +401,29 @@ 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(ClientTickEndPacket.class, ClientTickEndPacket::new,
map(0x0B, MINECRAFT_1_21_2, false));
serverbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
map(0x09, MINECRAFT_1_7_2, false),
map(0x17, MINECRAFT_1_9, false),
@@ -398,7 +436,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 +452,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 +468,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 +484,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 +495,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 +512,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 +525,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 +540,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 +557,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 +574,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 +591,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 +608,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 +626,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 +640,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;
@@ -47,38 +50,53 @@ public class FallbackPreparer {
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt();
public static final int VEHICLE_ENTITY_ID = RANDOM.nextInt();
public static final CompoundBinaryTag MAP_ITEM_NBT = CompoundBinaryTag.builder()
.put("map", IntBinaryTag.intBinaryTag(0)) // map type
.build();
public static final BlockType[] POSSIBLE_BLOCK_TYPES = BlockType.values();
public final FallbackPacket[] BLOCKS_PACKETS = new FallbackPacket[POSSIBLE_BLOCK_TYPES.length];
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;
@@ -93,14 +111,10 @@ public class FallbackPreparer {
public FallbackPacket alreadyVerifying;
public FallbackPacket reconnectedTooFast;
public FallbackPacket protocolBlacklisted;
public FallbackPacket invalidUsername;
public FallbackPacket unsupportedVersion;
public FallbackPacket tooManyOnlinePerIP;
public FallbackPacket removeBoat;
public FallbackPacket teleportMinecart;
public FallbackPacket spawnBoatEntity;
public FallbackPacket spawnMinecartEntity;
public FallbackPacket setPassengers;
public FallbackPacket[] xpCountdown;
public FallbackPacket updateTime;
public static int maxMovementTick, dynamicSpawnYPosition, maxTotalPacketsSent;
@@ -110,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();
@@ -132,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;
@@ -154,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));
unsupportedVersion = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getUnsupportedVersion(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getTooManyOnlinePerIp(), true));
// Prepare transfer packet
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("verification.transfer.enabled")) {
@@ -179,17 +200,13 @@ public class FallbackPreparer {
transferToOrigin = null;
}
// Prepare packets for the vehicle check
removeBoat = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
teleportMinecart = new TeleportEntityPacket(
VEHICLE_ENTITY_ID, SPAWN_X_POSITION, IN_VOID_Y_POSITION, SPAWN_Z_POSITION, false);
spawnBoatEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_ENTITY_ID,
EntityType.BOAT, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
spawnMinecartEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_ENTITY_ID,
EntityType.MINECART, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
setPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_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");
@@ -250,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;
}
}
@@ -36,251 +36,272 @@ 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;
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;
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;
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;
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;
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;
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;
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> id;
@@ -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
}
}
@@ -15,16 +15,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.command;
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import io.netty.buffer.ByteBuf;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@Getter
@RequiredArgsConstructor
@ToString(of = "rawArguments")
public final class CommandInvocation {
private final InvocationSource source;
private final String[] rawArguments;
public final class ClientTickEndPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
}
@Override
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
// This packet is always empty
return 0;
}
}
@@ -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().apply(protocolVersion);
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
writeVarInt(byteBuf, blockId);
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().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();
@@ -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
@@ -73,11 +73,15 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
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.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;
}
@@ -87,27 +91,18 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
private final boolean enableGravityCheck, enableCollisionsCheck;
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.channel().pipeline().get(FallbackPacketDecoder.class);
decoder.setListener(new FallbackProtocolHandler(user));
}
private int movementTick, clientTick, expectedTeleportId = FIRST_TELEPORT_ID;
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,50 @@ 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);
} else if (packet instanceof ClientTickEndPacket) {
clientTick++;
}
}
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) {
@@ -178,7 +194,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.
@@ -186,6 +202,11 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
checkState(Math.abs(Math.abs(x) - BLOCKS_PER_ROW) < BLOCKS_PER_ROW, "illegal x offset: " + x);
checkState(Math.abs(Math.abs(z) - BLOCKS_PER_ROW) < BLOCKS_PER_ROW, "illegal z offset: " + z);
// Check if the client is ticking correctly
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
checkState(clientTick >= movementTick, "invalid ticking: " + clientTick + "/" + movementTick);
}
if (!onGround) {
// The deltaY is 0 whenever the player sends their first position packet.
// We have to account for this or the player will falsely fail the verification.
@@ -193,7 +214,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
@@ -229,7 +250,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) {
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.channel().flush();
}
private void markSuccess() {
// Pass the player to the next verification handler
final var decoder = user.channel().pipeline().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,42 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
}
}
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 markSuccess() {
if (user.channel().isActive()) {
if (!Sonar.get().getConfig().getVerification().getValidNameRegex().matcher(user.getUsername()).matches()) {
user.disconnect(Sonar.get().getConfig().getVerification().getInvalidUsername());
return;
}
// 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 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 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 +188,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.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() {
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.channel().pipeline().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(setPassengers);
user.channel().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.channel().pipeline().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.channel().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(setPassengers);
user.channel().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,12 +53,11 @@ 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);
@@ -68,7 +67,7 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
user.channel().pipeline().remove(FallbackVarInt21FrameDecoder.class);
user.channel().pipeline().remove(FallbackVarIntLengthEncoder.class);
} else {
closeWith(user.channel(), 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;
}
}
@@ -31,16 +31,15 @@ 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 @NotNull Component component) {
this.serializedComponent = GsonComponentSerializer.gson().serialize(component);
this.modernJson = GsonComponentSerializer.gson().serialize(component);
this.legacyJson = GsonComponentSerializer.colorDownsamplingGson().serialize(component);
this.binaryTag = serialize(new JsonParser().parse(this.modernJson));
}
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java
@@ -141,14 +140,12 @@ public final class ComponentHolder {
return EndBinaryTag.endBinaryTag();
}
public void write(final 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,23 @@ 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 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 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 {
@@ -111,7 +108,7 @@ public class ProtocolUtil {
if ((value & (0xFFFFFFFF << 7)) == 0) {
byteBuf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
final int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
byteBuf.writeShort(w);
} else {
writeVarIntFull(byteBuf, value);
@@ -224,9 +221,8 @@ public class ProtocolUtil {
public static void closeWith(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final Object msg) {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0
&& protocolVersion.compareTo(MINECRAFT_1_7_2) >= 0) {
final @NotNull Object msg) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
channel.eventLoop().execute(() -> {
channel.config().setAutoRead(false);
channel.eventLoop().schedule(() -> {
@@ -284,15 +280,7 @@ 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 @NotNull ByteBuf byteBuf, final byte @NotNull [] bytes) {
checkState(bytes.length < Short.MAX_VALUE, "Too long array");
writeVarInt(byteBuf, bytes.length);
byteBuf.writeBytes(bytes);
}
@@ -304,49 +292,21 @@ public class ProtocolUtil {
}
}
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 +320,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;
@@ -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
@@ -163,6 +163,12 @@ verification:
# - CREATIVE: სიცოცხლის და საჭმლის მაჩვენებლები იშლება
# - ADVENTURE: ყველა UI კომპონენტი ჩანს
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
@@ -186,7 +192,7 @@ verification:
write-timeout: 10000
# რამდენ ხანს უნდა დაელოდოს მოთამაშე ვერიფიკაციის დროს ხელახლა დაკავშირებამდე?
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
rejoin-delay: 8000
rejoin-delay: 5000
# რამდენ ხანს უნდა ახსოვდეს Sonar-ს მოთამაშე რამდენჯერ იყო წარუმატებელი ვერიფიკაციის დროს?
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
remember-time: 120000
@@ -164,6 +164,12 @@ verification:
# - CREATIVE: gezondheid en honger zijn verborgen
# - ADVENTURE: alle UI-componenten zijn zichtbaar
gamemode: ADVENTURE
# Zomertijd in de verificatiewereld
# Je kunt alle zomertijd hier vinden:
# https://minecraft.wiki/w/Daylight_cycle
# Dit is alleen een visuele instelling en verandert niets aan het verificatieproces!
# Als deze waarde is ingesteld op 1000, wordt het pakket niet verzonden.
time-of-day: 1000
# Sonar cached de meeste pakketten die het naar de client stuurt, inclusief het LoginSuccess pakket.
# Welke gebruikersnaam moet Sonar gebruiken voor het LoginSucces pakket?
# De gebruikersnaam zal niet zichtbaar zijn voor de speler zonder client aanpassingen
@@ -184,7 +190,7 @@ verification:
read-timeout: 8000
# De tijd sinds de speler stopte met het verzenden van pakketten voordat de verbinding werd verbroken
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
rejoin-delay: 8000
rejoin-delay: 5000
# De tijd sinds de server stopte met het versturen van pakketten voordat de verbinding met de speler werd verbroken
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
write-timeout: 10000
@@ -164,6 +164,12 @@ verification:
# - CREATIVE: pasek życia i głodu jest ukryty
# - ADVENTURE: wszystkie komponenty interfejsu są widoczne
gamemode: ADVENTURE
# Czas letni w weryfikowanym świecie
# Wszystkie czasy letnie można znaleźć tutaj:
# https://minecraft.wiki/w/Daylight_cycle
# Jest to tylko ustawienie wizualne i nie zmienia procesu weryfikacji!
# Jeśli ta wartość jest ustawiona na 1000, pakiet nie zostanie wysłany.
time-of-day: 1000
# Sonar buforuje większość pakietów, które wysyła do klienta, w tym pakiet LoginSuccess.
# Jakiej nazwy użytkownika powinien używać Sonar dla pakietu LoginSuccess?
# Nazwa użytkownika nie będzie widoczna dla gracza bez modyfikacji klienta
@@ -187,7 +193,7 @@ verification:
write-timeout: 10000
# Jak długo niezweryfikowany gracz powinien czekać przed ponownym połączeniem do serwera?
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
rejoin-delay: 8000
rejoin-delay: 5000
# Jak długo Sonar powinien zapamiętywać liczbę nieudanych weryfikacji gracza?
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
remember-time: 120000
@@ -163,6 +163,12 @@ verification:
# - CREATIVE: здоровье и голод скрыты
# - ADVENTURE: все компоненты пользовательского интерфейса видны
gamemode: ADVENTURE
# Летнее время в мире верификации
# Вы можете найти все летнее время здесь:
# https://minecraft.wiki/w/Daylight_cycle
# Это только визуальная настройка и не меняет процесс верификации!
# Если это значение установлено в 1000, пакет не будет отправлен.
time-of-day: 1000
# Sonar кэширует большинство пакетов, которые он отправляет клиенту, включая пакет LoginSuccess.
# Какое имя пользователя должен использовать Sonar для пакета LoginSuccess?
# Имя пользователя не будет видно игроку без модификации клиента
@@ -186,7 +192,7 @@ verification:
write-timeout: 10000
# Сколько времени игрок должен ждать перед повторным подключением во время проверки
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
rejoin-delay: 8000
rejoin-delay: 5000
# Как долго Sonar должен помнить количество неудачных проверок для игрока?
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
remember-time: 120000
+29 -22
View File
@@ -11,10 +11,10 @@
# 需要帮助吗? https://jonesdev.xyz/discord
# 源代码: https://github.com/jonesdevelopment/sonar
# 文件: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, 以 GPL-3.0 许可
# Copyright Jones Development and Sonar Contributors, 以 GPL-3.0 许可证分发
#
# 未归类的设置
# 常规的设置
general:
# Sonar 是否应该自动检查新的更新?
# Sonar 使用 GitHub API 检查当前版本是否过时
@@ -24,18 +24,19 @@ general:
# 具有相同 IP 地址的在线玩家的最大数量
max-online-per-ip: 3
# 攻击检测器的敏感度设置
# 只有当您知道自己在做什么的时候才编辑它
# 攻击检测器
# 只有当您知道自己在做什么的时候才应该编辑它
attack-tracker:
# 在一段时间内至少需要 x 个新玩家加入才满足触发条件.
# 在一段时间内至少需要多少个新玩家加入才满足触发条件?
min-players-for-attack: 8
# 至少经过多少毫秒后才可以解除遭受攻击模式
# (该值为毫秒. 1秒等于1000毫秒.)
min-attack-duration: 30000
# 攻击检测器要重复检测到多少次攻击 才能确认服务器正在遭受攻击?
# 要重复检测到多少次攻击 才应该被确认为服务器正在遭受攻击?
# 该值作为缓冲区 以避免可能的误判
min-attack-threshold: 2
# 当从遭受攻击标记为解除攻击时 应该需要等待多久才能再次被标记为正在遭受攻击?
# 当从遭受攻击状态标记为已缓解攻击状态时
# 至少需要多少毫秒才允许为对于可能检测到的攻击重新进入遭受攻击状态?
# (该值为毫秒. 1秒等于1000毫秒.)
attack-cooldown-delay: 3000
@@ -43,7 +44,7 @@ attack-tracker:
# 提示: IP 地址将以纯文本形式保存 请确保您的数据库安全!
database:
# 要连接到的数据库类型
# 可用类型: NONE (不使用数据库), MYSQL, MARIADB, H2
# 可用类型: NONE (不使用数据库), MYSQL, MARIADB, H2 (本地数据库)
type: NONE
# Sonar 插件目录中用于 H2 数据库的文件
filename: verified_players
@@ -72,31 +73,31 @@ verification:
# 可用类型: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: 玩家始终需要验证 直到他们通过验证为止
# - DURING_ATTACK: 玩家仅在服务器疑似遭到攻击时验证
# - NEVER: 玩家不会被验证
# - NEVER: 永远不检查未被验证过的玩家
timing: ALWAYS
checks:
# 检查玩家的下落移动是否遵循原版客户端.
gravity:
# Sonar 是否应该检查玩家的下落移动? (推荐启用)
enabled: true
# 当玩家验证失败时 是否向他们发送验证码?
# 当玩家验证失败时 是否向他们发送地图验证码?
captcha-on-fail: false
# 玩家跌落在平台上多少tick后才能被算作站在平台上?
max-movement-ticks: 8
# 检查玩家是否与下方生成的方块发生碰撞
# 检查玩家是否可以正确地站在方块上
collision:
# Sonar 是否应该生成方块平台以检查玩家下落与方块之间的碰撞? (推荐启用)
enabled: true
# 检查客户端坐在实体上时是否发送了正确的数据包
vehicle:
# 声纳是否应该检查玩家是否正确进入船只?
# Sonar 是否应该让玩家坐在实体上, 以便检查他们的数据包是否合法?
enabled: true
# 客户端发送的最小数据包数量
# 客户端需要发送的所需的最小数据包数量
minimum-packets: 2
# 地图验证码
# 手持地图验证码
map-captcha:
# Sonar 应该在什么时候向玩家发送验证码?
# 可用类型: ALWAYS, DURING_ATTACK, NEVER
@@ -141,8 +142,8 @@ verification:
# 为此您必须在服务器软件的配置文件中允许接受来自transfer的连接.
# 此外 您可能希望减少 Velocity 或其它服务器软件/插件的登录速率限制
# 因为这可能会阻止玩家快速地重新连接到服务器
# 如果你的服务器允许 Bedrock 玩家通过间歇泉加入, 请确保为间歇泉设置了
# 传输工具 TransferTool, 以便将 Bedrock 玩家重定向到正确的服务器.
# 如果你的服务器允许 Bedrock 玩家通过 Geyser 加入, 请确保为 Geyser 设置了
# TransferTool 扩展, 以便将 Bedrock 玩家重定向到正确的服务器.
# https://github.com/onebeastchris/TransferTool
enabled: false
# 当玩家通过验证后, Sonar 应该将玩家转移到哪个服务器
@@ -153,22 +154,28 @@ verification:
# 如果服务器地址不包含端口 请保留25565.
destination-port: 25565
# 玩家在虚拟服务器中的游戏模式
# 玩家正在验证时所使用的游戏模式
# 可用类型: SURVIVAL, CREATIVE, ADVENTURE
# - SURVIVAL: 生存模式, 所有UI可见
# - CREATIVE: 创造模式, 隐藏血量和饱食度
# - ADVENTURE: 冒险模式, 所有UI可见
gamemode: ADVENTURE
# 验证过程中玩家所看到的当前世界中的一天时间
# 您可以在这里找到您想要的时间:
# https://minecraft.wiki/w/Daylight_cycle
# 该选项不会改变验证过程.
# 如果该值设置为 1000, 则不会发送数据包.
time-of-day: 1000
# Sonar 缓存它发送到客户端的大部分数据包, 包括 LoginSuccess 数据包.
# Sonar 应该使用哪个用户名来接收 LoginSuccess 数据包?
# 如果不修改客户端, 用户名将不会对玩家可见
cached-username: "Sonar"
# 当 Sonar 尝试验证玩家时 是否在控制台中打印它?
# 当 Sonar 尝试验证玩家时 是否在控制台中打印有关玩家的信息?
log-connections: true
# Sonar 是否应该记录在攻击期间内的验证会话?
# Sonar 是否应该在攻击期间内打印日志?
log-during-attack: false
# Sonar 是否应该记录验证过程中玩家的移动位置的变化?
# 有助于发现错误 但不建议在非调试环境下的服务器启用该选项.
# 有助于发现错误 但不建议在生产环境下的服务器启用该选项.
debug-xyz-positions: false
# Sonar 是否应该检查来自 Geyser 的(基岩版)玩家?
# 此功能是实验性的. 请报告任何因为误判而导致无法通过检查的问题
@@ -180,9 +187,9 @@ verification:
# 服务器在断开玩家连接前停止发送数据包的时间
# (该值为毫秒. 1秒等于1000毫秒.)
write-timeout: 10000
# 当玩家在需要验证时(即还未通过检查的情况下)需要等待多少毫秒才可以重新连接?
# 当玩家在还未通过检查的情况下断开连接后需要等待多少毫秒才可以重新连接?
# (该值为毫秒. 1秒等于1000毫秒.)
rejoin-delay: 8000
rejoin-delay: 5000
# 当玩家验证失败后, Sonar 应该记住他多久?
# (该值为毫秒. 1秒等于1000毫秒.)
remember-time: 120000
@@ -199,6 +199,7 @@ verification:
incorrect: '<prefix><red>Zadali jste špatnou odpověď! Zkuste to prosím znovu.'
# Zpráva zobrazená hráči při ověřování
# Nastavením této hodnoty na '' uvítací zprávu zcela vypnete
welcome: '<prefix><green>Jste ověřováni. Prosím, nehýbejte se; jedná se o automatický proces.'
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, zatímco captcha ještě není připravena
@@ -207,6 +208,11 @@ verification:
- <yellow>Vaše anti-bot data ještě nejsou připravena.
- <gray>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
- <footer>
# Zpráva o odpojení, která se zobrazí, když se někdo připojí s nepodporovanou verzí Minecraftu
unsupported-version:
- <header>
- <red>Tento server nepodporuje vaši verzi Minecraftu.
- <footer>
# Zpráva o odpojení, která se zobrazí, když se někdo znovu připojí příliš rychle během ověření
too-fast-reconnect:
- <header>
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff. Show more