Compare commits

..
79 Commits
Author SHA1 Message Date
Jones a0b14214ae Release 2.1.18 (#418) 2024-12-01 14:00:32 +01:00
Jones 3d96a32f81 Fix crash issue on 1.13-1.13.2
(Backported minor changes from 3.0.0)
2024-12-01 01:23:54 +01:00
Jones de67ab12c2 Downgrade mysql-connector-j to 9.0.0 2024-11-30 19:46:08 +01:00
Jones adef9d9ccb Release 2.1.17 (#417) 2024-11-26 17:06:20 +01:00
Jones 71b40af656 Bump mariadb-java-client from 3.4.1 to 3.5.1 2024-11-23 17:07:21 +01:00
Jones 3a79711a8b Bump mysql-connector-j from 9.0.0 to 9.1.0 2024-11-23 17:06:29 +01:00
Jones 3094f12f84 Bump Gradle from 8.11 to 8.11.1 2024-11-23 17:02:51 +01:00
Jones a74c3da8d4 Use version name instead of ID for logging 2024-11-23 15:13:48 +01:00
dependabot[bot] 427768fc43 Bump io.netty:netty-all from 4.1.114.Final to 4.1.115.Final (#412)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.114.Final to 4.1.115.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.114.Final...netty-4.1.115.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-11-18 03:57:07 +01:00
dependabot[bot] b0d7dfb735 Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#413)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.34 to 1.18.36.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.34...v1.18.36)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  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-18 03:56:53 +01:00
Jones ff9216c4cc Add spotless for codestyle (#410)
* Add spotless for codestyle

* Fix license header file

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:03:32 +02:00
135 changed files with 2083 additions and 1972 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>
+2 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) ${YEAR} Sonar Contributors
* 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
@@ -14,3 +14,4 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-3
View File
@@ -14,6 +14,3 @@ tasks {
archiveFileName = "sonar-api-${rootProject.version}.jar"
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -75,28 +75,23 @@ public interface Sonar {
void reload();
@NotNull
default SonarVersion getVersion() {
@NotNull default SonarVersion getVersion() {
return SonarVersion.INSTANCE;
}
@NotNull
default SonarEventManager getEventManager() {
@NotNull default SonarEventManager getEventManager() {
return SonarEventManager.INSTANCE;
}
@NotNull
default AttackTracker getAttackTracker() {
@NotNull default AttackTracker getAttackTracker() {
return AttackTracker.INSTANCE;
}
@NotNull
default Fallback getFallback() {
@NotNull default Fallback getFallback() {
return Fallback.INSTANCE;
}
@NotNull
static Sonar get() {
return SonarSupplier.get();
@NotNull static Sonar get() {
return SonarSupplier.sonar;
}
}
@@ -19,20 +19,15 @@ package xyz.jonesdev.sonar.api;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.exception.AlreadyInitializedException;
@UtilityClass
public class SonarSupplier {
private Sonar _sonar;
Sonar sonar;
public void set(final @NotNull Sonar instance) {
if (_sonar != null) {
throw new AlreadyInitializedException("Sonar is already initialized");
public void set(final @NotNull Sonar _sonar) {
if (sonar != null) {
throw new IllegalStateException("Sonar is already initialized");
}
_sonar = instance;
}
public @NotNull Sonar get() {
return _sonar;
sonar = _sonar;
}
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.command;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
@Getter
@RequiredArgsConstructor
@ToString(of = "rawArguments")
public final class CommandInvocation {
private final InvocationSource source;
private final String[] rawArguments;
}
@@ -30,17 +30,15 @@ import xyz.jonesdev.sonar.api.update.UpdateChecker;
import java.util.*;
import static java.util.Collections.emptyList;
public interface SonarCommand {
List<String> TAB_SUGGESTIONS = new ArrayList<>();
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
Map<Integer, List<String>> TAB_SUGGESTIONS = new LinkedHashMap<>();
default void handle(final @NotNull InvocationSource source, final String @NotNull [] args) {
// Check if the player actually has the permission to run the command
if (source.isPlayer() && !source.getPermissionFunction().test("sonar.command")) {
source.sendMessage(Sonar.get().getConfig().getNoPermission());
if (Sonar.get().getConfig().getNoPermission() != null) {
source.sendMessage(Sonar.get().getConfig().getNoPermission());
}
return;
}
@@ -103,29 +101,28 @@ public interface SonarCommand {
}
static void prepareCachedTabSuggestions() {
// Don't re-cache tab suggestions
if (!TAB_SUGGESTIONS.isEmpty()) return;
// Cache tab suggestions
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
TAB_SUGGESTIONS.add(subcommand.getInfo().name());
if (subcommand.getInfo().aliases().length > 0) {
TAB_SUGGESTIONS.addAll(Arrays.asList(subcommand.getInfo().aliases()));
}
final List<String> arguments = Arrays.asList(subcommand.getInfo().arguments());
ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), arguments);
for (final String alias : subcommand.getInfo().aliases()) {
ARG_TAB_SUGGESTIONS.put(alias, arguments);
if (TAB_SUGGESTIONS.isEmpty()) {
final List<String> subcommandsAndAliases = new ArrayList<>();
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
subcommandsAndAliases.add(subcommand.getInfo().name());
final List<String> arguments = Arrays.asList(subcommand.getInfo().arguments());
TAB_SUGGESTIONS.put(subcommand.getInfo().name().hashCode(), arguments);
for (final String alias : subcommand.getInfo().aliases()) {
subcommandsAndAliases.add(alias);
TAB_SUGGESTIONS.put(alias.hashCode(), arguments);
}
}
TAB_SUGGESTIONS.put(-1, subcommandsAndAliases);
}
}
default List<String> getCachedTabSuggestions(final String @NotNull [] arguments) {
if (arguments.length <= 1) {
return TAB_SUGGESTIONS;
} else if (arguments.length == 2) {
final String subcommandName = arguments[0].toLowerCase();
return ARG_TAB_SUGGESTIONS.getOrDefault(subcommandName, emptyList());
if (arguments.length <= 2) {
final int subcommandHash = arguments.length <= 1 ? -1 : arguments[0].toLowerCase().hashCode();
return TAB_SUGGESTIONS.getOrDefault(subcommandHash, Collections.emptyList());
}
return emptyList();
return Collections.emptyList();
}
}
@@ -23,7 +23,6 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.CommandInvocation;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import java.util.Objects;
@@ -91,7 +90,7 @@ public abstract class Subcommand {
}
// Execute the sub command from the invocation source with the given arguments
execute(new CommandInvocation(invocationSource, arguments));
execute(invocationSource, arguments);
}
public final @NotNull String getDescription() {
@@ -99,5 +98,5 @@ public abstract class Subcommand {
return Objects.requireNonNull(Sonar.get().getConfig().getMessagesConfig().getString(path));
}
protected abstract void execute(final @NotNull CommandInvocation invocation);
protected abstract void execute(final @NotNull InvocationSource source, final String @NotNull [] args);
}
@@ -195,6 +195,7 @@ public final class SonarConfiguration {
verification.brand.validRegex = Pattern.compile(generalConfig.getString("verification.checks.client-brand.valid-regex"));
verification.brand.maxLength = generalConfig.getInt("verification.checks.client-brand.max-length");
verification.timeOfDay = clamp(generalConfig.getInt("verification.time-of-day"), 0, 24000);
verification.gamemode = Verification.Gamemode.valueOf(generalConfig.getString("verification.gamemode"));
verification.validNameRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-name-regex"));
verification.validLocaleRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-locale-regex"));
@@ -219,16 +220,12 @@ public final class SonarConfiguration {
webhook.content = webhookConfig.getString("webhook.content");
webhook.footer.text = webhookConfig.getString("webhook.embed.footer.text");
webhook.footer.iconUrl = webhookConfig.getString("webhook.embed.footer.icon-url");
final String realEmbedPath = "webhook.embed";
final String embedPath = realEmbedPath + ".";
webhook.embed.title = webhookConfig.getString(embedPath + "title");
webhook.embed.titleUrl = webhookConfig.getString(embedPath + "title-url");
webhook.embed.description = String.join("\n", webhookConfig.getStringList(embedPath + "description"));
webhook.embed.r = webhookConfig.getInt(embedPath + "color.red");
webhook.embed.g = webhookConfig.getInt(embedPath + "color.green");
webhook.embed.b = webhookConfig.getInt(embedPath + "color.blue");
webhook.embed.title = webhookConfig.getString("webhook.embed.title");
webhook.embed.titleUrl = webhookConfig.getString("webhook.embed.title-url");
webhook.embed.description = String.join("\n", webhookConfig.getStringList("webhook.embed.description"));
webhook.embed.r = webhookConfig.getInt("webhook.embed.color.red");
webhook.embed.g = webhookConfig.getInt("webhook.embed.color.green");
webhook.embed.b = webhookConfig.getInt("webhook.embed.color.blue");
if (!webhook.url.isEmpty()) {
if (webhook.username.isEmpty()) {
@@ -242,8 +239,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 +262,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 +412,7 @@ public final class SonarConfiguration {
}
}
private int timeOfDay;
private boolean checkGeyser;
private boolean logConnections;
private boolean logDuringAttack;
@@ -432,6 +437,7 @@ public final class SonarConfiguration {
private Component blacklisted;
private Component protocolBlacklisted;
private Component currentlyPreparing;
private Component unsupportedVersion;
}
@Getter
@@ -452,7 +458,7 @@ public final class SonarConfiguration {
Library.builder()
.groupId("org{}mariadb{}jdbc")
.artifactId("mariadb-java-client")
.version("3.4.1")
.version("3.5.1")
.relocate("org{}mariadb", "xyz{}jonesdev{}sonar{}libs{}mariadb")
.build()),
H2("H2", "jdbc:h2:file:%s", new H2DatabaseTypeAdapter(),
@@ -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;
}
@@ -18,7 +18,6 @@
package xyz.jonesdev.sonar.api.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.util.ReferenceCountUtil;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
@@ -28,26 +27,17 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer;
import java.net.InetAddress;
public interface FallbackUser {
@NotNull
Channel getChannel();
@NotNull Channel channel();
@NotNull
ChannelPipeline getPipeline();
@NotNull InetAddress getInetAddress();
@NotNull
InetAddress getInetAddress();
@NotNull ProtocolVersion getProtocolVersion();
@NotNull
ProtocolVersion getProtocolVersion();
@NotNull SystemTimer getLoginTimer();
@NotNull
SystemTimer getLoginTimer();
@NotNull String getFingerprint();
@NotNull
String getFingerprint();
@NotNull
String getUsername();
@NotNull String getUsername();
boolean isForceCaptcha();
@@ -69,8 +59,8 @@ public interface FallbackUser {
* @param msg Message to send to the player
*/
default void write(final @NotNull Object msg) {
if (getChannel().isActive()) {
getChannel().writeAndFlush(msg, getChannel().voidPromise());
if (channel().isActive()) {
channel().writeAndFlush(msg, channel().voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
@@ -81,8 +71,8 @@ public interface FallbackUser {
* sent once all messages are flushed.
*/
default void delayedWrite(final @NotNull Object msg) {
if (getChannel().isActive()) {
getChannel().write(msg, getChannel().voidPromise());
if (channel().isActive()) {
channel().write(msg, channel().voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* 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
@@ -26,51 +26,53 @@ import java.util.Map;
@Getter
@RequiredArgsConstructor
public enum ProtocolVersion {
UNKNOWN(-1),
MINECRAFT_1_7_2(4),
MINECRAFT_1_7_6(5),
MINECRAFT_1_8(47),
MINECRAFT_1_9(107),
MINECRAFT_1_9_1(108),
MINECRAFT_1_9_2(109),
MINECRAFT_1_9_4(110),
MINECRAFT_1_10(210),
MINECRAFT_1_11(315),
MINECRAFT_1_11_1(316),
MINECRAFT_1_12(335),
MINECRAFT_1_12_1(338),
MINECRAFT_1_12_2(340),
MINECRAFT_1_13(393),
MINECRAFT_1_13_1(401),
MINECRAFT_1_13_2(404),
MINECRAFT_1_14(477),
MINECRAFT_1_14_1(480),
MINECRAFT_1_14_2(485),
MINECRAFT_1_14_3(490),
MINECRAFT_1_14_4(498),
MINECRAFT_1_15(573),
MINECRAFT_1_15_1(575),
MINECRAFT_1_15_2(578),
MINECRAFT_1_16(735),
MINECRAFT_1_16_1(736),
MINECRAFT_1_16_2(751),
MINECRAFT_1_16_3(753),
MINECRAFT_1_16_4(754),
MINECRAFT_1_17(755),
MINECRAFT_1_17_1(756),
MINECRAFT_1_18(757),
MINECRAFT_1_18_2(758),
MINECRAFT_1_19(759),
MINECRAFT_1_19_1(760),
MINECRAFT_1_19_3(761),
MINECRAFT_1_19_4(762),
MINECRAFT_1_20(763),
MINECRAFT_1_20_2(764),
MINECRAFT_1_20_3(765),
MINECRAFT_1_20_5(766),
MINECRAFT_1_21(767);
UNKNOWN(-1, "Unknown"),
MINECRAFT_1_7_2(4, "1.7.2"),
MINECRAFT_1_7_6(5, "1.7.6"),
MINECRAFT_1_8(47, "1.8"),
MINECRAFT_1_9(107, "1.9"),
MINECRAFT_1_9_1(108, "1.9.1"),
MINECRAFT_1_9_2(109, "1.9.2"),
MINECRAFT_1_9_4(110, "1.9.3"),
MINECRAFT_1_10(210, "1.10"),
MINECRAFT_1_11(315, "1.11"),
MINECRAFT_1_11_1(316, "1.11.1"),
MINECRAFT_1_12(335, "1.12"),
MINECRAFT_1_12_1(338, "1.12.1"),
MINECRAFT_1_12_2(340, "1.12.2"),
MINECRAFT_1_13(393, "1.13"),
MINECRAFT_1_13_1(401, "1.13.1"),
MINECRAFT_1_13_2(404, "1.13.2"),
MINECRAFT_1_14(477, "1.14"),
MINECRAFT_1_14_1(480, "1.14.1"),
MINECRAFT_1_14_2(485, "1.14.2"),
MINECRAFT_1_14_3(490, "1.14.3"),
MINECRAFT_1_14_4(498, "1.14.4"),
MINECRAFT_1_15(573, "1.15"),
MINECRAFT_1_15_1(575, "1.15.1"),
MINECRAFT_1_15_2(578, "1.15.2"),
MINECRAFT_1_16(735, "1.16"),
MINECRAFT_1_16_1(736, "1.16.1"),
MINECRAFT_1_16_2(751, "1.16.2"),
MINECRAFT_1_16_3(753, "1.16.3"),
MINECRAFT_1_16_4(754, "1.16.4"),
MINECRAFT_1_17(755, "1.17"),
MINECRAFT_1_17_1(756, "1.17.1"),
MINECRAFT_1_18(757, "1.18"),
MINECRAFT_1_18_2(758, "1.18.2"),
MINECRAFT_1_19(759, "1.19"),
MINECRAFT_1_19_1(760, "1.19.1"),
MINECRAFT_1_19_3(761, "1.19.3"),
MINECRAFT_1_19_4(762, "1.19.4"),
MINECRAFT_1_20(763, "1.20"),
MINECRAFT_1_20_2(764, "1.20.2"),
MINECRAFT_1_20_3(765, "1.20.3"),
MINECRAFT_1_20_5(766, "1.20.5"),
MINECRAFT_1_21(767, "1.21"),
MINECRAFT_1_21_2(768, "1.21.2");
private final int protocol;
private final String name;
public static final ProtocolVersion LATEST_VERSION;
public static final Map<Integer, ProtocolVersion> ID_TO_PROTOCOL_CONSTANT;
@@ -90,6 +92,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);
+18 -2
View File
@@ -3,7 +3,8 @@ import net.kyori.indra.git.IndraGitExtension
plugins {
java
alias(libs.plugins.shadow)
alias(libs.plugins.indra.git) apply true
alias(libs.plugins.indra.git)
alias(libs.plugins.spotless)
}
allprojects {
@@ -15,6 +16,7 @@ allprojects {
apply(plugin = "java")
apply(plugin = "com.gradleup.shadow")
apply(plugin = "com.diffplug.spotless")
dependencies {
compileOnly(rootProject.libs.lombok)
@@ -31,6 +33,17 @@ allprojects {
compileOnly(rootProject.libs.libby.core)
}
spotless {
java {
endWithNewline()
formatAnnotations()
removeUnusedImports()
trimTrailingWhitespace()
indentWithSpaces(2)
licenseHeaderFile("../HEADER")
}
}
tasks {
shadowJar {
// Set the file name of the shadowed jar
@@ -80,6 +93,9 @@ allprojects {
attributes["Git-Commit"] = gitCommit
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
}
}
@@ -87,7 +103,7 @@ tasks {
// This is a small wrapper tasks to simplify the building process
register("build-sonar") {
val subprojects = listOf("api", "captcha", "common", "bukkit", "bungeecord", "velocity")
val buildTasks = subprojects.flatMap { listOf("$it:clean", "$it:shadowJar") }
val buildTasks = subprojects.flatMap { listOf("$it:clean", "$it:spotlessApply", "$it:shadowJar") }
dependsOn(buildTasks)
}
}
-3
View File
@@ -46,6 +46,3 @@ tasks {
relocate("net.kyori", "xyz.jonesdev.sonar.libs.kyori")
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -27,10 +27,9 @@ import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.SonarCommand;
import java.util.Collections;
import java.util.List;
import static java.util.Collections.emptyList;
public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, SonarCommand {
@Override
@@ -54,9 +53,6 @@ public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, S
final String commandAlias,
final String @NotNull [] args) {
// Do not allow tab completion if the player does not have the required permission
if (!sender.hasPermission("sonar.command")) {
return emptyList();
}
return getCachedTabSuggestions(args);
return sender.hasPermission("sonar.command") ? getCachedTabSuggestions(args) : Collections.emptyList();
}
}
@@ -28,18 +28,19 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginStartPacket;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.net.InetSocketAddress;
import java.util.Objects;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INBOUND_HANDLER;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.DEBUG;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
private FallbackPacketRegistry.ProtocolRegistry registry;
FallbackBukkitInboundHandler() {
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
@@ -53,17 +54,13 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
};
}
private FallbackPacketRegistry.ProtocolRegistry registry;
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
public void updateRegistry(final @NotNull FallbackPacketRegistry registry,
final @NotNull ProtocolVersion protocolVersion) {
this.registry = registry.getProtocolRegistry(SERVERBOUND, protocolVersion);
this.registry = registry.getProtocolRegistry(FallbackPacketRegistry.Direction.SERVERBOUND, protocolVersion);
}
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof ByteBuf) {
final ByteBuf byteBuf = (ByteBuf) msg;
@@ -81,7 +78,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
return;
}
final int packetId = readVarInt(byteBuf);
final int packetId = ProtocolUtil.readVarInt(byteBuf);
final FallbackPacket packet = registry.createPacket(packetId);
// Skip the packet if it's not registered within Sonar's packet registry
@@ -95,13 +92,14 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
packet.decode(byteBuf, protocolVersion == null ? DEFAULT_PROTOCOL_VERSION : protocolVersion);
} catch (Throwable throwable) {
byteBuf.release();
throw DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
}
// Check if the packet still has bytes left after we decoded it
if (byteBuf.isReadable()) {
byteBuf.release();
throw DEBUG ? new DecoderException("Could not read packet to end") : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Could not read packet to end ("
+ byteBuf.readableBytes() + " bytes left)") : QuietDecoderException.INSTANCE;
}
/*
@@ -119,12 +117,13 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
case LOGIN:
case TRANSFER:
// Let the actual handler know about the handshake packet
handleHandshake(ctx.channel(), handshake.getHostname(), handshake.getProtocolVersionId());
handleHandshake(ctx, handshake.getHostname(), handshake.getProtocolVersionId());
// Be ready for the next packet (which is supposed to be a login packet)
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
break;
default:
throw DEBUG ? new DecoderException("Bad intent " + handshake.getIntent()) : QuietDecoderException.INSTANCE;
throw ProtocolUtil.DEBUG ? new DecoderException("Bad handshake intent " + handshake.getIntent())
: QuietDecoderException.INSTANCE;
}
} else if (packet instanceof LoginStartPacket) {
final LoginStartPacket loginStart = (LoginStartPacket) packet;
@@ -132,9 +131,10 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Let Sonar process the login packet
handleLogin(ctx.channel(), ctx, () -> {
handleLogin(ctx, () -> {
byteBuf.readerIndex(originalReaderIndex);
ctx.fireChannelRead(byteBuf.retain());
// TODO: recode this?
final ChannelHandler inboundHandler = ctx.pipeline().remove(FALLBACK_INBOUND_HANDLER);
if (inboundHandler != null) {
channelRemovalListener.accept(ctx.pipeline(), FALLBACK_INBOUND_HANDLER, inboundHandler);
@@ -26,8 +26,8 @@ import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-3
View File
@@ -30,6 +30,3 @@ tasks {
archiveFileName = "Sonar-Bungee.jar" // Hardcode a shortened version of the name
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -26,7 +26,7 @@ import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.InvocationSource;
import xyz.jonesdev.sonar.api.command.SonarCommand;
import static java.util.Collections.emptyList;
import java.util.Collections;
public final class BungeeSonarCommand extends Command implements TabExecutor, SonarCommand {
public BungeeSonarCommand() {
@@ -47,9 +47,6 @@ public final class BungeeSonarCommand extends Command implements TabExecutor, So
@Override
public Iterable<String> onTabComplete(final @NotNull CommandSender sender, final String @NotNull [] args) {
// Do not allow tab completion if the player does not have the required permission
if (!sender.hasPermission("sonar.command")) {
return emptyList();
}
return getCachedTabSuggestions(args);
return sender.hasPermission("sonar.command") ? getCachedTabSuggestions(args) : Collections.emptyList();
}
}
@@ -17,7 +17,6 @@
package xyz.jonesdev.sonar.bungee.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
@@ -26,8 +25,8 @@ import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.LoginRequest;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -49,42 +48,42 @@ final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
}
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof PacketWrapper) {
final PacketWrapper packetWrapper = (PacketWrapper) msg;
final DefinedPacket wrappedPacket = packetWrapper.packet;
// Skip any unknown or invalid packets
if (wrappedPacket != null) {
final Channel channel = ctx.channel();
// Intercept any handshake packet by the client
if (wrappedPacket instanceof Handshake) {
final Handshake handshake = (Handshake) wrappedPacket;
// We don't care about server pings; remove the handler
if (handshake.getRequestedProtocol() == STATUS) {
ctx.pipeline().remove(this);
} else {
handleHandshake(channel, handshake.getHost(), handshake.getProtocolVersion());
handleHandshake(ctx, handshake.getHost(), handshake.getProtocolVersion());
}
}
// Intercept any server login packet by the client
else if (wrappedPacket instanceof LoginRequest) {
} else if (wrappedPacket instanceof LoginRequest) {
// Make sure to use the potentially modified, real IP
final LoginRequest loginRequest = (LoginRequest) wrappedPacket;
final HandlerBoss handlerBoss = channel.pipeline().get(HandlerBoss.class);
final ChannelWrapper channelWrapper;
try {
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
} catch (final Throwable throwable) {
throw new ReflectiveOperationException(throwable);
final HandlerBoss handlerBoss = ctx.pipeline().get(HandlerBoss.class);
// This might sometimes happen if the client unexpectedly disconnects
if (handlerBoss != null) {
// Get the real IP address of the player through the channel wrapper
final ChannelWrapper channelWrapper;
try {
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
} catch (final Throwable throwable) {
throw new ReflectiveOperationException(throwable);
}
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Make sure to mark this packet as released
packetWrapper.trySingleRelease();
// Let Sonar process the login packet
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
handleLogin(ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
return;
}
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Make sure to mark this packet as released
packetWrapper.trySingleRelease();
// Let Sonar process the login packet
handleLogin(channel, ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
return;
}
}
}
-3
View File
@@ -5,6 +5,3 @@ dependencies {
testCompileOnly(project(":api"))
testCompileOnly(rootProject.libs.imagefilters)
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -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) {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* 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
-3
View File
@@ -3,6 +3,3 @@ dependencies {
implementation(project(":captcha"))
compileOnly(rootProject.libs.adventure.nbt)
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -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
@@ -49,11 +51,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
/**
* Validates and handles incoming handshake packets
*
* @param hostname Hostname (server address) sent by the client
* @param protocol Protocol version number sent by the client
*/
protected final void handleHandshake(final @NotNull Channel channel,
protected final void handleHandshake(final @NotNull ChannelHandlerContext ctx,
final @NotNull String hostname,
final int protocol) throws Exception {
// Check if the hostname is invalid
@@ -65,47 +64,47 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
throw QuietDecoderException.INSTANCE;
}
protocolVersion = ProtocolVersion.fromId(protocol);
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
ctx.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
}
/**
* Validates and handles incoming login packets
*
* @param channel Forwarded client channel
* @param loginPacket Login packet sent by the client
* @param username Username sent by the client
* @param socketAddress Socket address of the client
*/
protected final void handleLogin(final @NotNull Channel channel,
final @NotNull ChannelHandlerContext ctx,
final @NotNull Runnable loginPacket,
protected final void handleLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull Runnable initialLoginAction,
final @NotNull String username,
final @NotNull InetSocketAddress socketAddress) throws Exception {
// Check if the player has already sent a login packet
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
// 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();
channel.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
ctx.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
// Check if Fallback is already verifying a player with the same IP address
if (Sonar.get().getFallback().getConnected().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyVerifying);
customDisconnect(ctx.channel(), alreadyVerifying, protocolVersion);
return;
}
// Check if the protocol ID of the player is not allowed to enter the server
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols().contains(protocolVersion.getProtocol())) {
customDisconnect(channel, protocolVersion, protocolBlacklisted);
customDisconnect(ctx.channel(), protocolBlacklisted, protocolVersion);
return;
}
@@ -115,34 +114,34 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
if (limit > 0) {
final int score = Sonar.get().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
if (score >= limit) {
customDisconnect(channel, protocolVersion, blacklisted);
customDisconnect(ctx.channel(), blacklisted, protocolVersion);
return;
}
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Completely skip Geyser connections if configured
final boolean geyser = GeyserUtil.isGeyserConnection(channel, socketAddress);
final boolean geyser = GeyserUtil.isGeyserConnection(ctx.channel(), socketAddress);
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Make sure we actually have to verify the player
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Check if the IP address is currently being rate-limited
if (!Sonar.get().getFallback().getRatelimiter().attempt(inetAddress)) {
customDisconnect(channel, protocolVersion, reconnectedTooFast);
customDisconnect(ctx.channel(), reconnectedTooFast, protocolVersion);
return;
}
@@ -150,25 +149,41 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
rewriteProtocol(ctx, channelRemovalListener);
// Queue the connection for further processing
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, action) -> {
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
// Check if the player is already queued since we don't want bots to flood the queue
if (action != null) {
customDisconnect(channel, protocolVersion, alreadyQueued);
if (runnable != null) {
customDisconnect(ctx.channel(), alreadyQueued, protocolVersion);
// Remove other instances of this IP address from the queue
return null;
}
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(channel, protocolVersion, invalidUsername);
return;
}
// Create an instance for the user and let the verification handler take over the channel
return () -> new FallbackUserWrapper(ctx, inetAddress, protocolVersion, username, fingerprint, geyser);
});
}
// Create an instance for the user and let the verification handler take over the channel
new FallbackUserWrapper(channel, inetAddress, protocolVersion, username, fingerprint, geyser);
};
/**
* Executes the maximum accounts per IP limit check before letting the player join
*/
protected final void initialLogin(final @NotNull Channel channel,
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (count >= maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return count + 1;
}
}
// Let the server know about the login packet
loginPacket.run();
// Increment the number of accounts with the same IP
return count == null ? 1 : count + 1;
});
}
@@ -196,58 +211,25 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
TimeUnit.MILLISECONDS));
}
/**
* Executes the maximum accounts per IP limit check before letting the player join
*
* @param ctx Forwarded client channel context
* @param inetAddress Resolved client IP address
* @param loginPacket Login packet sent by the client
*/
protected final void initialLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (count >= maxOnlinePerIp) {
customDisconnect(ctx.channel(), protocolVersion, tooManyOnlinePerIP);
return count + 1;
}
}
// Let the server know about the login packet
loginPacket.run();
// Increment the number of accounts with the same IP
return count == null ? 1 : count + 1;
});
}
/**
* Disconnect the player before verification (during login)
* by replacing the encoder before running the method.
*
* @param packet Disconnect packet
* @param channel Channel of the player
* @param protocolVersion Protocol version of the player
*/
private void customDisconnect(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet) {
private static void customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final @NotNull ProtocolVersion protocolVersion) {
if (channel.isActive()) {
if (channel.eventLoop().inEventLoop()) {
_customDisconnect(channel, protocolVersion, packet);
_customDisconnect(channel, packet, protocolVersion);
} else {
channel.eventLoop().execute(() -> _customDisconnect(channel, protocolVersion, packet));
channel.eventLoop().execute(() -> _customDisconnect(channel, packet, protocolVersion));
}
}
}
private void _customDisconnect(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet) {
/**
* Disconnect the player before verification (during login)
*/
private static void _customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final @NotNull ProtocolVersion protocolVersion) {
// 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) {
@@ -260,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,10 +18,11 @@
package xyz.jonesdev.sonar.common.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
@@ -34,24 +35,22 @@ import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarIntLengthEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.DisconnectPacket;
import xyz.jonesdev.sonar.common.fallback.verification.FallbackPreJoinHandler;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.net.InetAddress;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_2;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.CONFIG;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.loginSuccess;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.closeWith;
@Getter
@ToString(of = {"protocolVersion", "inetAddress", "geyser"})
public final class FallbackUserWrapper implements FallbackUser {
@Accessors(fluent = true)
private final Channel channel;
private final ChannelPipeline pipeline;
private final InetAddress inetAddress;
private final ProtocolVersion protocolVersion;
private final String fingerprint;
@@ -61,14 +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.pipeline = channel.pipeline();
this.channel = ctx.channel();
this.inetAddress = inetAddress;
this.protocolVersion = protocolVersion;
this.username = username;
@@ -85,11 +83,11 @@ public final class FallbackUserWrapper implements FallbackUser {
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.connection")
.replace("<username>", username)
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
.replace("<protocol>", String.valueOf(protocolVersion.getProtocol())));
.replace("<protocol>", protocolVersion.getName()));
}
// Call the VerifyJoinEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(username, this));
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(this));
// Run this in the channel's event loop to avoid issues
channel.eventLoop().execute(() -> {
@@ -102,32 +100,42 @@ public final class FallbackUserWrapper implements FallbackUser {
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
// Replace normal encoder to allow custom packets
// TODO: recode injection
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Send LoginSuccess packet to make the client think they are joining the server
write(loginSuccess);
// The LoginSuccess packet has been sent, now we can change the registry state
newEncoder.updateRegistry(protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0 ? CONFIG : GAME);
channel.pipeline().addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
channel.pipeline().addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Replace normal decoder to allow custom packets
// TODO: recode injection
final FallbackPacketDecoder newDecoder = new FallbackPacketDecoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
pipeline.addLast(FALLBACK_PACKET_DECODER, newDecoder);
channel.pipeline().addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
channel.pipeline().addLast(FALLBACK_PACKET_DECODER, newDecoder);
// We're sending the LoginSuccess packet now
newDecoder.updateRegistry(FallbackPacketRegistry.LOGIN);
newEncoder.updateRegistry(FallbackPacketRegistry.LOGIN);
// Send LoginSuccess packet to make the client think they are joining the server
write(FallbackPreparer.loginSuccess);
// pre-1.20.2 clients do not have the configuration stage
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
newDecoder.updateRegistry(FallbackPacketRegistry.GAME);
newEncoder.updateRegistry(FallbackPacketRegistry.GAME);
}
// Listen for all incoming packets by setting the packet listener
newDecoder.setListener(new FallbackPreJoinHandler(this));
// Make sure to catch all exceptions during the verification
pipeline.addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
// TODO: recode injection
channel.pipeline().addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
});
}
@Override
public void disconnect(final @NotNull Component reason) {
final FallbackPacketEncoder encoder = pipeline.get(FallbackPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() != GAME;
closeWith(channel, protocolVersion, DisconnectPacket.create(reason, duringLogin));
final FallbackPacketEncoder encoder = channel.pipeline().get(FallbackPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() == FallbackPacketRegistry.LOGIN;
ProtocolUtil.closeWith(channel, protocolVersion, new DisconnectPacket(reason, duringLogin));
}
}
@@ -24,9 +24,9 @@ import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandler;
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -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;
@@ -44,13 +47,8 @@ import java.util.UUID;
public class FallbackPreparer {
private final Random RANDOM = new Random();
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt(50);
public static final int VEHICLE_BOAT_ENTITY_ID = PLAYER_ENTITY_ID + 1 + RANDOM.nextInt(10);
public static final int VEHICLE_MINECART_ENTITY_ID = VEHICLE_BOAT_ENTITY_ID + 1 + RANDOM.nextInt(10);
public static final CompoundBinaryTag MAP_ITEM_NBT = CompoundBinaryTag.builder()
.put("map", IntBinaryTag.intBinaryTag(0)) // map type
.build();
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt();
public static final int VEHICLE_ENTITY_ID = RANDOM.nextInt();
public static final BlockType[] POSSIBLE_BLOCK_TYPES = BlockType.values();
public final FallbackPacket[] BLOCKS_PACKETS = new FallbackPacket[POSSIBLE_BLOCK_TYPES.length];
@@ -58,28 +56,48 @@ public class FallbackPreparer {
public final int BLOCKS_PER_ROW = 8; // 8 * 8 = 64 (vanilla protocol maximum)
public final int SPAWN_X_POSITION = 16 / 2; // middle of the chunk
public final int SPAWN_Z_POSITION = 16 / 2; // middle of the chunk
public final int DEFAULT_Y_COLLIDE_POSITION = 1 + RANDOM.nextInt(255); // 255 is the maximum Y position
public final int IN_AIR_Y_POSITION = 1000 + RANDOM.nextInt(338); // High altitude (randomized)
public final int IN_VOID_Y_POSITION = -(120 + RANDOM.nextInt(120)); // Low altitude (randomized)
public final int PLATFORM_Y_POSITION = 1 + RANDOM.nextInt(255); // 255 is the maximum Y position
public final int IN_AIR_Y_POSITION = 3000 + RANDOM.nextInt(500); // Random high altitude
public final int FIRST_TELEPORT_ID = RANDOM.nextInt();
public final int SECOND_TELEPORT_ID = RANDOM.nextInt();
public final int PRE_JOIN_KEEP_ALIVE_ID = RANDOM.nextInt();
private final int MAP_SLOT = RANDOM.nextInt(9);
public static final FallbackPacket CAPTCHA_SET_CONTAINER_SLOT = new SetContainerSlotPacket(
0, 36 + MAP_SLOT, 1, ItemType.FILLED_MAP, CompoundBinaryTag.builder()
.put("map", IntBinaryTag.intBinaryTag(0)) // map id
.build());
public final static FallbackPacket CAPTCHA_HELD_ITEM_SLOT = new SetHeldItemPacket(MAP_SLOT);
public final FallbackPacket DEFAULT_ABILITIES = new PlayerAbilitiesPacket(0x00, 0, 0);
public final FallbackPacket NO_MOVE_ABILITIES = new PlayerAbilitiesPacket(0x02, 0, 0);
public final FallbackPacket NO_MOVE_ABILITIES_BEDROCK = new PlayerAbilitiesPacket(0x06, 0, 0);
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION, 0, 90, 0, 0, false));
SPAWN_X_POSITION, 10000, SPAWN_Z_POSITION, 0, 90, 0, 0, false, false, true));
public final FallbackPacket EMPTY_CHUNK_DATA = new FallbackPacketSnapshot(new ChunkDataPacket(0, 0));
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfigurationPacket();
public final FallbackPacket REGISTRY_SYNC_LEGACY = new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null));
public final FallbackPacket PRE_JOIN_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(PRE_JOIN_KEEP_ALIVE_ID));
public final FallbackPacket[] REGISTRY_SYNC_1_20 = new FallbackPacket[] {
new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null))};
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20);
public final FallbackPacket[] REGISTRY_SYNC_1_21 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21);
public final FallbackPacket[] REGISTRY_SYNC_1_21_2 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_2);
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final static FallbackPacket CACHED_HELD_ITEM_SLOT = new SetHeldItemPacket(~4);
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new KeepAlivePacket(RANDOM.nextInt());
public final static FallbackPacket INVALID_HELD_ITEM_SLOT = new SetHeldItemPacket(-1);
public final FallbackPacket RANDOM_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(RANDOM.nextInt()));
public static final FallbackPacket SPAWN_BOAT_ENTITY = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_ENTITY_ID, EntityType.BOAT, SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, 0, 0, 0));
public static final FallbackPacket SPAWN_MINECART_ENTITY = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_ENTITY_ID, EntityType.MINECART, SPAWN_X_POSITION, IN_AIR_Y_POSITION - 16, SPAWN_Z_POSITION,
0, 0, 0, 0));
public static final FallbackPacket REMOVE_VEHICLE = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
public static final FallbackPacket SET_VEHICLE_PASSENGERS = new FallbackPacketSnapshot(
new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
public static FallbackPacket loginSuccess;
public FallbackPacket loginSuccess;
public FallbackPacket welcomeMessage;
public FallbackPacket enterCodeMessage;
public FallbackPacket incorrectCaptcha;
public static FallbackPacket joinGame;
@@ -93,15 +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 setBoatPassengers;
public FallbackPacket setMinecartPassengers;
public FallbackPacket[] xpCountdown;
public FallbackPacket updateTime;
public static int maxMovementTick, dynamicSpawnYPosition, maxTotalPacketsSent;
@@ -111,16 +124,23 @@ public class FallbackPreparer {
Sonar.get().getLogger().info("Preloading all registered packets...");
FallbackPacketRegistry.values();
// Prepare LoginSuccess packet
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(UUID.randomUUID(),
Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username")));
// Prepare LoginSuccess packet with capped username to 16 characters
final UUID uuid = UUID.randomUUID();
String username = Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username");
if (username.length() > 16) {
username = username.substring(0, 16);
}
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(uuid, username, true));
// Prepare JoinGame packet
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGamemode().getId(), RANDOM.nextLong(),
0, 0, false, true, false,
false, false, false, true,
new String[]{"minecraft:overworld"}, "minecraft:overworld", "flat"));
Sonar.get().getConfig().getVerification().getGamemode().getId(),
-1, 0, 0,
RANDOM.nextInt(3), 1, 0, 0,
new String[]{"lol"}, "lol", "flat",
DimensionType.OVERWORLD, RANDOM.nextLong() & 1337,
false, true, false,
false, false, false, true));
// Prepare the gravity check
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
@@ -133,18 +153,18 @@ public class FallbackPreparer {
}
// Set the dynamic block and collide Y position based on the maximum fall distance
dynamicSpawnYPosition = DEFAULT_Y_COLLIDE_POSITION + (int) Math.ceil(fallDistance);
dynamicSpawnYPosition = PLATFORM_Y_POSITION + (int) Math.ceil(fallDistance);
defaultSpawnPosition = new FallbackPacketSnapshot(new SetDefaultSpawnPositionPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION));
spawnPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, 0, FIRST_TELEPORT_ID, 0, false));
0, 0, FIRST_TELEPORT_ID, 0, false, false, true));
fallStartPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, dynamicSpawnYPosition - IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, -90, SECOND_TELEPORT_ID, 1 << 1, false));
0, -90, SECOND_TELEPORT_ID, 1 << 1, false, false, true));
fallStartPositionLegacy = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION,
0, -90, 0, 0, false));
0, -90, 0, 0, false, false, true));
// Prepare collision platform positions
final int length = BLOCKS_PER_ROW / 2;
@@ -155,21 +175,21 @@ public class FallbackPreparer {
for (int x = 0; x < BLOCKS_PER_ROW; x++) {
for (int z = 0; z < BLOCKS_PER_ROW; z++) {
final BlockUpdate.BlockPosition position = new BlockUpdate.BlockPosition(
x + length, DEFAULT_Y_COLLIDE_POSITION, z + length, 0, 0);
x + length, PLATFORM_Y_POSITION, z + length, 0, 0);
blocks[index++] = new BlockUpdate(position, POSSIBLE_BLOCK_TYPES[i]);
}
}
BLOCKS_PACKETS[i] = new UpdateSectionBlocksPacket(0, 0, blocks);
BLOCKS_PACKETS[i] = new FallbackPacketSnapshot(new UpdateSectionBlocksPacket(0, 0, blocks));
}
// Prepare disconnect packets during login
blacklisted = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true));
invalidUsername = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getInvalidUsername(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getTooManyOnlinePerIp(), true));
blacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true));
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")) {
@@ -180,18 +200,23 @@ public class FallbackPreparer {
transferToOrigin = null;
}
// Prepare packets for the vehicle check
removeBoat = new RemoveEntitiesPacket(VEHICLE_BOAT_ENTITY_ID);
teleportMinecart = new TeleportEntityPacket(
VEHICLE_MINECART_ENTITY_ID, SPAWN_X_POSITION, IN_VOID_Y_POSITION, SPAWN_Z_POSITION, false);
spawnBoatEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_BOAT_ENTITY_ID,
EntityType.BOAT, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
spawnMinecartEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_MINECART_ENTITY_ID,
EntityType.MINECART, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
setBoatPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_BOAT_ENTITY_ID, PLAYER_ENTITY_ID));
setMinecartPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_MINECART_ENTITY_ID, PLAYER_ENTITY_ID));
// Prepare update time packet
final int timeOfDay = Sonar.get().getConfig().getVerification().getTimeOfDay();
if (timeOfDay != 1000) {
updateTime = new UpdateTimePacket(0L, timeOfDay, false);
} else {
updateTime = null;
}
// If the welcome message is empty, we don't need to send a message to the player
final String welcome = Sonar.get().getConfig().getMessagesConfig().getString("verification.welcome");
if (welcome.isEmpty()) {
welcomeMessage = null;
} else {
welcomeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(welcome,
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
}
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
@@ -242,4 +267,17 @@ public class FallbackPreparer {
+ Sonar.get().getConfig().getVerification().getMap().getMaxTries()
+ 150 /* some arbitrary leeway */;
}
public static FallbackPacket[] getRegistryPackets(final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
return REGISTRY_SYNC_1_21_2;
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21)) {
return REGISTRY_SYNC_1_21;
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
return REGISTRY_SYNC_1_20_5;
}
return REGISTRY_SYNC_1_20;
}
}
@@ -19,7 +19,6 @@ package xyz.jonesdev.sonar.common.fallback.protocol.block;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.function.Function;
@@ -27,6 +26,7 @@ import java.util.function.Function;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// TODO: load mappings from a separate file
@Getter
@SuppressWarnings("unused")
@RequiredArgsConstructor
public enum BlockType {
@@ -36,258 +36,274 @@ public enum BlockType {
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
//STONE(protocolVersion -> 1, 1),
ENCHANTMENT_TABLE(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 116;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 4612;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 4613;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 5116;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 5132;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 5136;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 5333;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 5719;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 7159;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 7385;
}
return 7389;
}, 0.75f),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 7389;
}
return 7619;
}, protocolVersion -> (double) 0.75f),
TRAPDOOR(protocolVersion -> {
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
if (protocolVersion.compareTo(MINECRAFT_1_7_6) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_7_6)) {
return 96;
}
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 167;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 6509;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 6510;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 7016;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 7552;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 7556;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 7802;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 8293;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 9937;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 10269;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 10273;
}
return 10414;
}, 0.1875),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 10414;
}
return 10749;
}, protocolVersion -> 0.1875),
END_PORTAL_FRAME(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 120;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 4633;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 4634;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 5137;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 5153;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 5157;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 5358;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 5744;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 7184;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 7410;
}
return 7414;
}, 0.8125),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 7414;
}
return 7644;
}, protocolVersion -> 0.8125),
DAYLIGHT_SENSOR(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 151;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 5651;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 5652;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 6158;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 6694;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 6698;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 6916;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 7327;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 8811;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 9063;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 9067;
}
return 9207;
}, 0.375),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 9207;
}
return 9462;
}, protocolVersion -> 0.375),
COBBLESTONE_WALL(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 139;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 5196;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 5197;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 5700;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 5660;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 5664;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 5866;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 6252;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 7692;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 7918;
}
return 7922;
}, 1.5),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 7922;
}
return 8152;
}, protocolVersion -> 1.5),
STONE_SLABS(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 44;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 7296;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 7297;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 7809;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 8345;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 8349;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 8595;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 9092;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 10748;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 11086;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 11090;
}
return 11231;
}, 0.5),
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 11231;
}
return 11566;
}, protocolVersion -> 0.5),
WHITE_CARPET(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 171;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 6823;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 6824;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 7330;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_1)) {
return 7866;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 7870;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 8116;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 8607;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 10251;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 10583;
}
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20)) {
return 10587;
}
return 10728;
}, 0.0625);
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 10728;
}
return 11063;
}, protocolVersion -> protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 0 : 0.0625);
private final Function<ProtocolVersion, Integer> idFunction;
@Getter
private final double blockHeight;
public int getId(final @NotNull ProtocolVersion protocolVersion) {
return idFunction.apply(protocolVersion);
}
private final Function<ProtocolVersion, Integer> id;
private final Function<ProtocolVersion, Double> blockHeight;
}
@@ -19,12 +19,11 @@ package xyz.jonesdev.sonar.common.fallback.protocol.block;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
@Getter
public final class BlockUpdate {
private final @NotNull BlockPosition position;
private final BlockPosition position;
private final BlockType blockType;
private final int legacyBlockState, blockState;
@@ -39,7 +38,6 @@ public final class BlockUpdate {
}
@Getter
@ToString
@RequiredArgsConstructor
public static final class BlockPosition {
private final int x, y, z;
@@ -18,10 +18,8 @@
package xyz.jonesdev.sonar.common.fallback.protocol.dimension;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
@@ -38,9 +36,7 @@ public final class DimensionRegistry {
public final CompoundBinaryTag CODEC_1_19_4;
public final CompoundBinaryTag CODEC_1_20;
public final CompoundBinaryTag CODEC_1_21;
public final DimensionInfo DEFAULT_DIMENSION_1_16_2;
public final DimensionInfo DEFAULT_DIMENSION_1_18_2;
public final CompoundBinaryTag CODEC_1_21_2;
static {
CODEC_1_16 = getCodec("codec_1_16.nbt");
@@ -51,9 +47,7 @@ public final class DimensionRegistry {
CODEC_1_19_4 = getCodec("codec_1_19_4.nbt");
CODEC_1_20 = getCodec("codec_1_20.nbt");
CODEC_1_21 = getCodec("codec_1_21.nbt");
DEFAULT_DIMENSION_1_16_2 = getDimension(CODEC_1_16_2);
DEFAULT_DIMENSION_1_18_2 = getDimension(CODEC_1_18_2);
CODEC_1_21_2 = getCodec("codec_1_21_2.nbt");
}
private @NotNull CompoundBinaryTag getCodec(final @NotNull String fileName) {
@@ -64,10 +58,4 @@ public final class DimensionRegistry {
throw new IllegalStateException(throwable);
}
}
private static @NotNull DimensionInfo getDimension(final @NotNull CompoundBinaryTag tag) {
final ListBinaryTag dimensions = tag.getCompound("minecraft:dimension_type").getList("value");
final BinaryTag elementTag = ((CompoundBinaryTag) dimensions.get(0)).get("element");
return new DimensionInfo("minecraft:overworld", 0, (CompoundBinaryTag) elementTag);
}
}
@@ -19,14 +19,14 @@ package xyz.jonesdev.sonar.common.fallback.protocol.dimension;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import net.kyori.adventure.nbt.CompoundBinaryTag;
@Getter
@ToString
@RequiredArgsConstructor
public final class DimensionInfo {
private final String identifier;
private final int id;
private final CompoundBinaryTag tag;
public enum DimensionType {
OVERWORLD("minecraft:overworld", 0, 0),
THE_NETHER("minecraft:the_nether", -1, 1),
THE_END("minecraft:the_end", 1, 2);
private final String key;
private final int legacyId, id;
}
@@ -17,8 +17,8 @@
package xyz.jonesdev.sonar.common.fallback.protocol.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.function.Function;
@@ -26,66 +26,69 @@ import java.util.function.Function;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// TODO: load mappings from a separate file
@Getter
@RequiredArgsConstructor
public enum EntityType {
// Useful resources:
// - https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/
// - https://pokechu22.github.io/Burger/1.20.4.html
BOAT(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 1;
}
if (protocolVersion.compareTo(MINECRAFT_1_14_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_14_4)) {
return 5;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 6;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 7;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 8;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_3)) {
return 9;
}
return 10;
}),
MINECART(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 10;
}
if (protocolVersion.compareTo(MINECRAFT_1_14_4) <= 0) {
return 85; // 1.21.2 split the boat type in id registries.
}),
MINECART(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 10;
}
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_14_4)) {
return 41;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 42;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 45;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 50;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 53;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 54;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_2)) {
return 64;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_3)) {
return 65;
}
return 69;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 69;
}
return 82;
});
private final Function<ProtocolVersion, Integer> function;
public int getId(final @NotNull ProtocolVersion protocolVersion) {
return function.apply(protocolVersion);
}
private final Function<ProtocolVersion, Integer> id;
}
@@ -15,10 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
package xyz.jonesdev.sonar.common.fallback.protocol.item;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.function.Function;
@@ -26,6 +26,7 @@ import java.util.function.Function;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// TODO: load mappings from a separate file
@Getter
@RequiredArgsConstructor
public enum ItemType {
// Useful resources:
@@ -33,45 +34,45 @@ public enum ItemType {
// - https://pokechu22.github.io/Burger/1.20.4.html
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
FILLED_MAP(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 358;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_1)) {
return 608;
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
return 613;
}
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_15_2)) {
return 671;
}
if (protocolVersion.compareTo(MINECRAFT_1_16_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_16_4)) {
return 733;
}
if (protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_18_2)) {
return 847;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_1)) {
return 886;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_3)) {
return 914;
}
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_19_4)) {
return 937;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_2) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_2)) {
return 941;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_3) <= 0) {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_20_3)) {
return 979;
}
return 982;
});
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 982;
}
return 1022;
}, protocolVersion -> protocolVersion.greaterThanOrEquals(MINECRAFT_1_21_2) ? 36 : 26);
private final Function<ProtocolVersion, Integer> idFunction;
public int getId(final @NotNull ProtocolVersion protocolVersion) {
return idFunction.apply(protocolVersion);
}
private final Function<ProtocolVersion, Integer> id;
private final Function<ProtocolVersion, Integer> components;
}
@@ -15,16 +15,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
package xyz.jonesdev.sonar.common.fallback.protocol.map;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.MapDataPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
@Getter
public final class MapCaptchaInfo {
private final String answer;
@@ -42,22 +41,21 @@ public final class MapCaptchaInfo {
}
this.legacy = new FallbackPacket[grid.length];
for (int i = 0; i < grid.length; i++) {
this.legacy[i] = new MapDataPacket(grid[i], i, 0);
this.legacy[i] = new MapDataPacket(grid[i], i, 0, 0, true);
}
// Prepare 1.8+ map data
this.modern = new MapDataPacket(buffer, 0, 0);
this.modern = new MapDataPacket(buffer, 0, 0, 0, true);
}
public void delayedWrite(final @NotNull FallbackUser user) {
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_8)) {
// 1.7.2-1.7.10 needs separate packets for each axis
for (final FallbackPacket legacyPacket : legacy) {
user.delayedWrite(legacyPacket);
for (final FallbackPacket packet : legacy) {
user.delayedWrite(packet);
}
return;
}
// Send modern packet
user.delayedWrite(modern);
}
}
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
package xyz.jonesdev.sonar.common.fallback.protocol.map;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +26,7 @@ import java.awt.image.Raster;
@UtilityClass
public class MapColorPalette {
// https://minecraft.wiki/w/Map_item_format#Full_color_tables
// TODO: load mappings from a separate file
public static final int[] COLOR_MAP = {
-1,
-1,
@@ -32,7 +32,7 @@ public final class FinishConfigurationPacket implements FallbackPacket {
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
// This packet is always empty; see https://wiki.vg/Protocol#Finish_Configuration
return 0;
}
@@ -26,12 +26,11 @@ import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketSnapshot;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.util.ArrayList;
import java.util.List;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@@ -42,19 +41,19 @@ public final class RegistryDataPacket implements FallbackPacket {
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_5) < 0) {
writeNamelessCompoundTag(byteBuf, tag);
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, tag);
} else if (type != null) {
writeString(byteBuf, type);
writeVarInt(byteBuf, bundles.size());
ProtocolUtil.writeString(byteBuf, type);
ProtocolUtil.writeVarInt(byteBuf, bundles.size());
for (final RegistryDataPacket.Bundle bundle : bundles) {
writeString(byteBuf, bundle.getName());
ProtocolUtil.writeString(byteBuf, bundle.getName());
// Write the bundle tag
final CompoundBinaryTag tag = bundle.getTag();
if (tag != null) {
byteBuf.writeBoolean(true);
writeNamelessCompoundTag(byteBuf, tag);
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, tag);
} else {
byteBuf.writeBoolean(false);
}
@@ -74,7 +73,8 @@ public final class RegistryDataPacket implements FallbackPacket {
final ArrayList<RegistryDataPacket.Bundle> bundles = new ArrayList<>();
for (final BinaryTag binaryTag : rootTag.getCompound(type).getList("value")) {
final CompoundBinaryTag tag = (CompoundBinaryTag) binaryTag;
bundles.add(new Bundle(tag.getString("name"), tag.getCompound("element")));
// CompoundBinaryTag#getCompound(String) will return an empty compound tag when not exist.
bundles.add(new Bundle(tag.getString("name"), tag.get("element") == null ? null : tag.getCompound("element")));
}
packets[index++] = new FallbackPacketSnapshot(new RegistryDataPacket(rootTag, type, bundles));
}
@@ -22,9 +22,7 @@ import lombok.Getter;
import lombok.ToString;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString(of = {"protocolVersionId", "hostname", "port"})
@@ -49,9 +47,9 @@ public final class HandshakePacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
protocolVersionId = readVarInt(byteBuf);
hostname = readString(byteBuf, MAXIMUM_HOSTNAME_LENGTH);
protocolVersionId = ProtocolUtil.readVarInt(byteBuf);
hostname = ProtocolUtil.readString(byteBuf, MAXIMUM_HOSTNAME_LENGTH);
port = byteBuf.readUnsignedShort();
intent = readVarInt(byteBuf);
intent = ProtocolUtil.readVarInt(byteBuf);
}
}
@@ -33,7 +33,7 @@ public final class LoginAcknowledgedPacket implements FallbackPacket {
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
// This packet is always empty; see https://wiki.vg/Protocol#Login_Acknowledged
return 0;
}
@@ -18,6 +18,7 @@
package xyz.jonesdev.sonar.common.fallback.protocol.packets.login;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -26,11 +27,11 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.util.UUID;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@@ -46,31 +47,32 @@ public final class LoginStartPacket implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
username = readString(byteBuf, 16);
username = ProtocolUtil.readString(byteBuf, 16);
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
if (byteBuf.readBoolean()) {
readPlayerKey(byteBuf);
final long expiry = byteBuf.readLong();
final long now = System.currentTimeMillis();
if (expiry < now) {
throw ProtocolUtil.DEBUG ? new DecoderException("Bad expiry: " + expiry) : QuietDecoderException.INSTANCE;
}
final byte[] key = ProtocolUtil.readByteArray(byteBuf);
if (key.length == 0) {
throw ProtocolUtil.DEBUG ? new DecoderException("Empty key") : QuietDecoderException.INSTANCE;
}
final byte[] signature = ProtocolUtil.readByteArray(byteBuf, 4096);
if (signature.length == 0) {
throw ProtocolUtil.DEBUG ? new DecoderException("Empty signature") : QuietDecoderException.INSTANCE;
}
}
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
uuid = readUUID(byteBuf);
return;
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (byteBuf.readBoolean()) {
uuid = readUUID(byteBuf);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_1)) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_2) || byteBuf.readBoolean()) {
uuid = ProtocolUtil.readUUID(byteBuf);
}
}
}
}
private static void readPlayerKey(final @NotNull ByteBuf byteBuf) {
final long expiry = byteBuf.readLong();
final byte[] key = readByteArray(byteBuf);
final byte[] signature = readByteArray(byteBuf, 4096);
}
}
@@ -26,12 +26,10 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.FastUuidSansHyphens;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@@ -39,30 +37,29 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
public final class LoginSuccessPacket implements FallbackPacket {
private UUID uuid;
private String username;
private boolean strictErrorHandling;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
writeUUID(byteBuf, uuid);
} else if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0) {
writeUUIDIntArray(byteBuf, uuid);
} else if (protocolVersion.compareTo(MINECRAFT_1_7_6) >= 0) {
writeString(byteBuf, uuid.toString());
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16)) {
ProtocolUtil.writeUUID(byteBuf, uuid);
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_7_6)) {
ProtocolUtil.writeString(byteBuf, uuid.toString());
} else {
writeString(byteBuf, FastUuidSansHyphens.toString(uuid));
ProtocolUtil.writeString(byteBuf, FastUuidSansHyphens.toString(uuid));
}
// Cap the length of the username to 16 characters
if (username.length() > 16) {
username = username.substring(0, 16);
}
writeString(byteBuf, username);
ProtocolUtil.writeString(byteBuf, username);
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
writeVarInt(byteBuf, 0); // properties
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19)) {
// We don't need to send any properties to the client
ProtocolUtil.writeVarInt(byteBuf, 0);
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) >= 0) {
byteBuf.writeBoolean(false); // should authenticate
if (protocolVersion.equals(ProtocolVersion.MINECRAFT_1_20_5)
|| protocolVersion.equals(ProtocolVersion.MINECRAFT_1_21)) {
// Whether the client should disconnect on its own if it receives invalid data from the server
byteBuf.writeBoolean(strictErrorHandling);
}
}
@@ -22,9 +22,7 @@ import lombok.*;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@Setter
@@ -45,12 +43,12 @@ public final class AnimationPacket implements FallbackPacket {
@Override
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
entityId = byteBuf.readInt();
type = LegacyAnimationType.getById(byteBuf.readByte());
} else if (protocolVersion.compareTo(MINECRAFT_1_8) > 0) {
} else if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_8)) {
// Only 1.9+ clients have an offhand
hand = readVarInt(byteBuf);
hand = ProtocolUtil.readVarInt(byteBuf);
}
}
@@ -27,9 +27,7 @@ import net.kyori.adventure.nbt.LongArrayBinaryTag;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@@ -38,70 +36,46 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
public final class ChunkDataPacket implements FallbackPacket {
private int sectionX, sectionZ;
private static final byte[] SECTION_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 1, 0};
private static final byte[] LIGHT_BYTES = new byte[]{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, -1, -1, 0, 0};
private static final byte[] LEGACY_FILLER_BYTES_17 = new byte[2];
private static final byte[] LEGACY_FILLER_BYTES = new byte[256];
private static final byte[] MODERN_FILLER_BYTES = new byte[256 * 4];
// Prepare nbt for 1.18 and pre-1.18
private static final CompoundBinaryTag MODERN_TAG, LEGACY_TAG;
static {
MODERN_TAG = prepareNBT(false);
LEGACY_TAG = prepareNBT(true);
}
private static @NotNull CompoundBinaryTag prepareNBT(final boolean legacy) {
final long[] arrayData = new long[legacy ? 36 : 37];
final LongArrayBinaryTag longArray = LongArrayBinaryTag.longArrayBinaryTag(arrayData);
final CompoundBinaryTag motion = CompoundBinaryTag.builder()
.put("MOTION_BLOCKING", longArray)
.build();
return CompoundBinaryTag.builder()
.put("root", motion)
.build();
}
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeInt(sectionX);
byteBuf.writeInt(sectionZ);
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_17_1) <= 0) {
writeVarInt(byteBuf, 0); // mask
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17)) {
if (protocolVersion.lessThanOrEquals(ProtocolVersion.MINECRAFT_1_17_1)) {
ProtocolUtil.writeVarInt(byteBuf, 0); // mask
}
} else {
byteBuf.writeBoolean(true); // full chunk
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
&& protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
if (protocolVersion.inBetween(ProtocolVersion.MINECRAFT_1_16, ProtocolVersion.MINECRAFT_1_16_1)) {
byteBuf.writeBoolean(true); // ignore old data
}
if (protocolVersion.compareTo(MINECRAFT_1_8) > 0) {
writeVarInt(byteBuf, 0);
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
} else {
byteBuf.writeShort(1); // fix void chunk
}
}
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0) {
writeNamelessCompoundTag(byteBuf, MODERN_TAG);
} else {
writeCompoundTag(byteBuf, protocolVersion.compareTo(MINECRAFT_1_18) < 0 ? LEGACY_TAG : MODERN_TAG);
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_14)) {
final long[] motionBlockingData = new long[protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_18) ? 36 : 37];
final CompoundBinaryTag motionBlockingTag = CompoundBinaryTag.builder()
.put("MOTION_BLOCKING", LongArrayBinaryTag.longArrayBinaryTag(motionBlockingData))
.build();
final CompoundBinaryTag rootTag = CompoundBinaryTag.builder()
.put("root", motionBlockingTag)
.build();
if (protocolVersion.compareTo(MINECRAFT_1_15) >= 0 && protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
writeVarInt(byteBuf, 1024);
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, rootTag);
if (protocolVersion.inBetween(ProtocolVersion.MINECRAFT_1_15, ProtocolVersion.MINECRAFT_1_17_1)) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_16_2)) {
ProtocolUtil.writeVarInt(byteBuf, 1024);
for (int i = 0; i < 1024; i++) {
writeVarInt(byteBuf, 1);
ProtocolUtil.writeVarInt(byteBuf, 1);
}
} else {
for (int i = 0; i < 1024; i++) {
@@ -111,36 +85,42 @@ 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_13)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_15)) {
ProtocolUtil.writeByteArray(byteBuf, new byte[256 * 4]);
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_18)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
} 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.exception;
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
import org.jetbrains.annotations.NotNull;
import io.netty.buffer.ByteBuf;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
public final class AlreadyInitializedException extends RuntimeException {
public AlreadyInitializedException(final @NotNull String message) {
super(message);
public final class ClientTickEndPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
public Throwable fillInStackTrace() {
return this;
@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(protocolVersion);
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
writeVarInt(byteBuf, blockId);
final int blockId = block.getBlockType().getId().apply(protocolVersion);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_13)) {
ProtocolUtil.writeVarInt(byteBuf, blockId);
} else {
final int shiftedBlockId = blockId << 4;
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) {
byteBuf.writeShort(shiftedBlockId);
} else {
writeVarInt(byteBuf, shiftedBlockId);
ProtocolUtil.writeVarInt(byteBuf, shiftedBlockId);
}
}
}
@@ -74,17 +71,17 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
byteBuf.writeLong(((sectionX & 0x3FFFFFL) << 42) | (sectionY & 0xFFFFF) | ((sectionZ & 0x3FFFFFL) << 20));
// 1.20+ don't have light update suppression
if (protocolVersion.compareTo(MINECRAFT_1_20) < 0) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20)) {
byteBuf.writeBoolean(true); // suppress light updates
}
writeVarInt(byteBuf, blockUpdates.length);
ProtocolUtil.writeVarInt(byteBuf, blockUpdates.length);
for (final BlockUpdate block : blockUpdates) {
// https://wiki.vg/Protocol#Update_Section_Blocks
final int shiftedBlockId = block.getBlockType().getId(protocolVersion) << 12;
final int shiftedBlockId = block.getBlockType().getId().apply(protocolVersion) << 12;
final long positionIdValue = shiftedBlockId | block.getBlockState();
writeVarLong(byteBuf, positionIdValue);
ProtocolUtil.writeVarLong(byteBuf, positionIdValue);
}
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public final class UpdateTimePacket implements FallbackPacket {
private long worldAge, timeOfDay;
private boolean tickDayTime;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
byteBuf.writeLong(worldAge);
byteBuf.writeLong(timeOfDay);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
byteBuf.writeBoolean(tickDayTime);
}
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
}
@@ -26,21 +26,21 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.ratelimit.Ratelimiter;
import java.net.InetAddress;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
// Idea taken from Velocity
@Setter
@RequiredArgsConstructor
public final class CaffeineCacheRatelimiter implements Ratelimiter<InetAddress> {
private Cache<InetAddress, Long> expiringCache;
private long timeout;
private Cache<InetAddress, Long> expiringCache;
public CaffeineCacheRatelimiter(final long timeout, final @NotNull TimeUnit unit) {
public CaffeineCacheRatelimiter(final @NotNull Duration duration) {
this.timeout = duration.toNanos();
this.expiringCache = Caffeine.newBuilder()
.ticker(Ticker.systemTicker())
.expireAfterWrite(timeout, unit)
.expireAfterWrite(duration)
.build();
this.timeout = unit.toNanos(timeout);
}
@Override
@@ -20,11 +20,9 @@ package xyz.jonesdev.sonar.common.fallback.verification;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.common.fallback.protocol.CaptchaPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.ItemType;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.MapCaptchaInfo;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetContainerSlotPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.map.MapCaptchaInfo;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetPlayerPositionPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetPlayerPositionRotationPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SystemChatPacket;
@@ -45,10 +43,9 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
this.tries = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
// If the player is on Java, set the 5th slot (ID 4) in the player's hotbar to the map
// If the player is on Bedrock, set the 1st slot (ID 0) in the player's hotbar to the map
final int slotId = user.isGeyser() ? 36 : 40;
user.delayedWrite(new SetContainerSlotPacket(0, slotId, 1, ItemType.FILLED_MAP, MAP_ITEM_NBT));
// We may not be on slot 4, so we need to make sure to synchronize the current slot
user.delayedWrite(CAPTCHA_HELD_ITEM_SLOT);
user.delayedWrite(CAPTCHA_SET_CONTAINER_SLOT);
// Send random captcha to the player
final MapCaptchaInfo captcha = CaptchaPreparer.getRandomCaptcha();
this.answer = captcha.getAnswer().toLowerCase();
@@ -60,7 +57,7 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
// Make sure the player knows that they have to enter the code in chat
user.delayedWrite(enterCodeMessage);
// Send all packets in one flush
user.getChannel().flush();
user.channel().flush();
}
private final String answer;
@@ -99,7 +96,7 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
if (keepAliveStreak++ > 20) {
keepAliveStreak = 0;
// Send a KeepAlive packet to prevent timeout
user.write(CAPTCHA_KEEP_ALIVE);
user.write(RANDOM_KEEP_ALIVE);
}
}
}
@@ -20,12 +20,12 @@ package xyz.jonesdev.sonar.common.fallback.verification;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import static xyz.jonesdev.sonar.api.config.SonarConfiguration.Verification.Gamemode.CREATIVE;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
public final class FallbackGravityHandler extends FallbackVerificationHandler {
@@ -49,11 +49,11 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
user.delayedWrite(DEFAULT_ABILITIES);
}
// Write the DefaultSpawnPosition packet to the buffer
if (user.getProtocolVersion().compareTo(MINECRAFT_1_19_3) >= 0) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_19_3)) {
user.delayedWrite(defaultSpawnPosition);
}
// Teleport the player to the position where we're starting to check them
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
user.delayedWrite(spawnPosition);
user.delayedWrite(fallStartPosition);
} else {
@@ -62,7 +62,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
}
// 1.20.3+ introduced game events
// Make sure the client knows that we're sending chunks next
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_3) >= 0) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_3)) {
user.delayedWrite(START_WRITING_CHUNKS);
}
// Teleport player into an empty world by sending an empty chunk packet
@@ -70,14 +70,18 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
// Spawn the invisible platform below the player
if (enableCollisionsCheck) {
final int index = RANDOM.nextInt(BLOCKS_PACKETS.length);
blockHeight = POSSIBLE_BLOCK_TYPES[index].getBlockHeight();
blockHeight = POSSIBLE_BLOCK_TYPES[index].getBlockHeight().apply(user.getProtocolVersion());
user.delayedWrite(BLOCKS_PACKETS[index]);
}
// Update the world time, if necessary
if (updateTime != null) {
user.delayedWrite(updateTime);
}
// Send all packets at once
user.getChannel().flush();
user.channel().flush();
// 1.8 and below don't have TeleportConfirm packets, which is why we're skipping that check.
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_9)) {
// Enable the movement checks
teleported = true;
}
@@ -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.getPipeline().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) {
@@ -157,6 +173,11 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
// Synchronize the Y coordinate
this.y = dynamicSpawnYPosition;
checkMovement = true;
// Send the welcome message to the player
if (welcomeMessage != null) {
user.write(welcomeMessage);
}
return;
}
@@ -173,7 +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.
@@ -181,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.
@@ -188,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
@@ -224,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) {
user.getChannel().eventLoop().schedule(this::markSuccess, 100L, TimeUnit.MILLISECONDS);
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_8)) {
user.channel().eventLoop().schedule(this::markSuccess, 100L, TimeUnit.MILLISECONDS);
} else {
sendKeepAlive();
/*
* The purpose of this KeepAlive packet is to confirm that the connection
* is active and legitimate, thereby preventing bot connections that
* could flood the server with login attempts and other unwanted traffic.
*/
user.write(PRE_JOIN_KEEP_ALIVE);
}
}
}
private boolean receivedClientInfo, receivedClientBrand, acknowledgedLogin;
private int expectedKeepAliveId;
/**
* The purpose of these KeepAlive packets is to confirm that the connection
* is active and legitimate, thereby preventing bot connections that
* could flood the server with login attempts and other unwanted traffic.
*/
private void sendKeepAlive() {
// Send a KeepAlive packet with a random ID
expectedKeepAliveId = RANDOM.nextInt();
user.write(new KeepAlivePacket(expectedKeepAliveId));
}
private void markAcknowledged() {
acknowledgedLogin = true;
// Update state, so we're able to send/receive configuration packets
updateEncoderDecoderState(FallbackPacketRegistry.CONFIG);
synchronizeClientRegistry();
// Write the FinishConfiguration packet to the buffer
user.delayedWrite(FINISH_CONFIGURATION);
// Send all packets in one flush
user.getChannel().flush();
}
private void markSuccess() {
// Pass the player to the next verification handler
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
decoder.setListener(new FallbackGravityHandler(user, this));
}
void validateClientInformation() {
checkState(receivedClientInfo, "didn't send client settings");
// Don't check Geyser players for plugin messages as they don't have them
if (!user.isGeyser()) {
checkState(receivedClientBrand, "didn't send plugin message");
}
}
private int expectedKeepAliveId = PRE_JOIN_KEEP_ALIVE_ID;
@Override
public void handle(final @NotNull FallbackPacket packet) {
@@ -106,26 +73,39 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
checkState(keepAliveId == expectedKeepAliveId,
"expected K ID " + expectedKeepAliveId + " but got " + keepAliveId);
// Immediately verify the player if they do not need any configuration (pre-1.20.2)
if (expectedKeepAliveId != 0) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
markSuccess();
} else {
markAcknowledged();
}
}
// 1.8 clients send KeepAlive packets with the ID 0 every second
// while the player is in the "Downloading terrain" screen.
expectedKeepAliveId = 0;
// Immediately verify the player if they do not need any configuration (pre-1.20.2)
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) < 0) {
markSuccess();
}
} else if (packet instanceof LoginAcknowledgedPacket) {
// Prevent users from sending multiple LoginAcknowledged packets
checkState(!acknowledgedLogin, "sent duplicate login ack");
markAcknowledged();
// Update state, so we're able to send/receive packets during the CONFIG state
updateEncoderDecoderState(FallbackPacketRegistry.CONFIG);
// Perform the KeepAlive check now (config, not pre-config)
user.write(PRE_JOIN_KEEP_ALIVE);
} else if (packet instanceof FinishConfigurationPacket) {
validateClientInformation();
// Update the encoder and decoder state because we're currently in the CONFIG state
updateEncoderDecoderState(FallbackPacketRegistry.GAME);
if (!user.isGeyser()) {
validateClientInformation();
}
markSuccess();
} else if (packet instanceof ClientInformationPacket) {
final ClientInformationPacket clientInformation = (ClientInformationPacket) packet;
if (!user.isGeyser()) {
checkState(clientInformation.getViewDistance() >= 2,
"view distance: " + clientInformation.getViewDistance());
}
// Ensure that the client locale is correct
validateClientLocale(clientInformation.getLocale());
// Check if the player sent an unused bit flag in the skin section
@@ -137,8 +117,8 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
} else if (packet instanceof PluginMessagePacket) {
final PluginMessagePacket pluginMessage = (PluginMessagePacket) packet;
final boolean usingModernChannel = pluginMessage.getChannel().equals(BRAND_CHANNEL);
final boolean usingLegacyChannel = pluginMessage.getChannel().equals(BRAND_CHANNEL_LEGACY);
final boolean usingModernChannel = pluginMessage.getChannel().equals("minecraft:brand");
final boolean usingLegacyChannel = pluginMessage.getChannel().equals("MC|Brand");
// Skip this payload if it does not contain client brand information
if (!usingModernChannel && !usingLegacyChannel) {
@@ -150,7 +130,8 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
// Check if the channel is correct - 1.13 uses the new namespace
// system ('minecraft:' + channel) and anything below 1.13 uses
// the legacy namespace system ('MC|' + channel).
checkState(usingLegacyChannel || user.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0,
checkState(usingLegacyChannel
|| user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_13),
"illegal PluginMessage channel: " + pluginMessage.getChannel());
// Validate the client branding using a regex to filter unwanted characters.
@@ -162,26 +143,42 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
}
}
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
// Update the packet registry state in the encoder and decoder pipelines
user.getPipeline().get(FallbackPacketDecoder.class).updateRegistry(registry);
user.getPipeline().get(FallbackPacketEncoder.class).updateRegistry(registry);
private void markAcknowledged() {
acknowledgedLogin = true;
// Write the new RegistrySync packets to the buffer
for (final FallbackPacket packet : getRegistryPackets(user.getProtocolVersion())) {
user.delayedWrite(packet);
}
// Write the FinishConfiguration packet to the buffer
user.delayedWrite(FINISH_CONFIGURATION);
// Send all packets in one flush
user.channel().flush();
}
private void synchronizeClientRegistry() {
// 1.20.5+ adds new "game bundle features" which overcomplicate all of this...
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
// Write the new RegistrySync packets to the buffer
for (final FallbackPacket syncPacket : user.getProtocolVersion().compareTo(MINECRAFT_1_21) < 0
? REGISTRY_SYNC_1_20_5 : REGISTRY_SYNC_1_21) {
user.delayedWrite(syncPacket);
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;
}
} else {
// Write the old RegistrySync packet to the buffer
user.delayedWrite(REGISTRY_SYNC_LEGACY);
// Pass the player to the next verification handler
final FallbackGravityHandler gravityHandler = new FallbackGravityHandler(user, this);
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(gravityHandler);
}
}
void validateClientInformation() {
checkState(receivedClientInfo, "didn't send client settings");
checkState(receivedClientBrand, "didn't send plugin message");
}
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
// Update the packet registry state in the encoder and decoder pipelines
user.channel().pipeline().get(FallbackPacketDecoder.class).updateRegistry(registry);
user.channel().pipeline().get(FallbackPacketEncoder.class).updateRegistry(registry);
}
private void validateClientBrand(final byte @NotNull [] data) {
// Check if the client brand is too short. It has to have at least 2 bytes.
checkState(data.length > 1, "client brand is too short");
@@ -191,7 +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.getChannel().flush();
}
/**
* Uses EntityAnimation packets to check for the excepted Animation (SwingArm) from the client.
* <br>
* <a href="https://wiki.vg/Protocol#Entity_Animation">Wiki.vg - EntityAnimation</a>
* <a href="https://wiki.vg/Protocol#Swing_Arm">Wiki.vg - SwingArm</a>
*/
private void sendArmAnimation() {
waitingSwingArm = true;
user.write(new EntityAnimationPacket(PLAYER_ENTITY_ID, EntityAnimationPacket.Type.SWING_MAIN_ARM));
}
private void markSuccess() {
// Either send the player to the vehicle check,
// send the player to the CAPTCHA, or finish the verification.
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
// Pass the player to the next best verification handler
if (Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
decoder.setListener(new FallbackVehicleHandler(user));
} else if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
decoder.setListener(new FallbackCaptchaHandler(user));
} else {
// The player has passed all checks
finishVerification();
}
}
@Override
public void handle(final @NotNull FallbackPacket packet) {
if (packet instanceof TransactionPacket) {
final TransactionPacket transaction = (TransactionPacket) packet;
// Make sure random transactions aren't counted
checkState(expectedTransactionId <= 0, "unexpected transaction");
// Make sure we are expecting a transaction packet
checkState(waitingTransaction, "unexpected transaction: " + transaction.getTransactionId());
// Make sure the window ID is valid
checkState(transaction.getWindowId() == 0, "wrong window ID " + transaction.getWindowId());
checkState(transaction.getWindowId() == 0, "wrong window: " + transaction.getWindowId());
// Make sure the transaction was accepted
// This must - by vanilla protocol - always be accepted
checkState(transaction.isAccepted(), "didn't accept transaction");
@@ -138,7 +79,7 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
sendSetHeldItem();
}
expectedTransactionId = 0;
waitingTransaction = false;
} else if (packet instanceof SetHeldItemPacket) {
final SetHeldItemPacket heldItemPacket = (SetHeldItemPacket) packet;
@@ -146,10 +87,9 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
// Also check if the player sent an invalid slot which is impossible by vanilla protocol
checkState(slotId >= 0 && slotId <= 8, "slot out of range: " + slotId);
// Check if the player sent a duplicate slot packet which is impossible by vanilla protocol
checkState(slotId != currentClientSlotId, "invalid slot: " + slotId);
checkState(slotId != currentClientSlotId, "duplicate slot: " + slotId);
// Only continue checking if we're actually expecting a SetHeldItem packet
// The player can send a SetHeldItem packet by themselves -> exempt
if (expectedSlotId != -1
// Check if the slot ID matches the expected slot ID
// This can false flag if a player spams these packets, which is why we don't fail for this
@@ -165,21 +105,78 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
// Make sure we are awaiting an AnimationPacket packet
if (waitingSwingArm) {
final AnimationPacket animationPacket = (AnimationPacket) packet;
checkState(animationPacket.getHand() == AnimationPacket.MAIN_HAND,
"invalid hand " + animationPacket.getHand());
// Check that the 1.7 client is responding to the correct entity id and animation type.
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
// Check entity id is player itself and if the player is sending the correct animation type
if (animationPacket.getEntityId() == PLAYER_ENTITY_ID
&& animationPacket.getType() == AnimationPacket.LegacyAnimationType.SWING_ARM) {
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_8)) {
// Check if the entity ID is the player itself
checkState(animationPacket.getEntityId() == PLAYER_ENTITY_ID,
"invalid entity ID " + animationPacket.getEntityId());
// Check if the player is sending the correct animation type
if (animationPacket.getType() == AnimationPacket.LegacyAnimationType.SWING_ARM) {
markSuccess();
waitingSwingArm = false;
}
} else {
checkState(animationPacket.getHand() == AnimationPacket.MAIN_HAND,
"invalid hand " + animationPacket.getHand());
markSuccess();
waitingSwingArm = false;
}
}
}
}
/**
* Uses Transaction (ping) packets to check for an immediate, legitimate response from the client
* <br>
* <a href="https://wiki.vg/Protocol#Ping_.28configuration.29">Wiki.vg - Ping (configuration)</a>
* <a href="https://wiki.vg/Protocol#Ping_.28play.29">Wiki.vg - Ping (play)</a>
*/
private void sendTransaction() {
// Send a Transaction (Ping) packet with a random ID
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
user.write(new TransactionPacket(0, expectedTransactionId, false));
waitingTransaction = true;
}
/**
* Uses SetHeldItem packets to check for a legitimate response from the client
* <br>
* <a href="https://wiki.vg/Protocol#Set_Held_Item_.28serverbound.29">Wiki.vg - SetHeldItem (play)</a>
*/
private void sendSetHeldItem() {
// Send an invalid HeldItemChange packet to the player to see if the player responds at all
user.delayedWrite(INVALID_HELD_ITEM_SLOT);
// Increment the player's slot by a random slot, and then modulo it by the maximum slot (8)
expectedSlotId = (currentClientSlotId + 1 + RANDOM.nextInt(7)) % 8;
// Send two SetHeldItem packets with the same slot to check if the player responds with the correct slot.
// By vanilla protocol, the client does not respond to duplicate SetHeldItem packets.
// We can take advantage of this by sending two packets with the same content to check for a valid response.
final SetHeldItemPacket heldItemPacket = new SetHeldItemPacket(expectedSlotId);
user.delayedWrite(heldItemPacket);
user.delayedWrite(heldItemPacket);
user.channel().flush();
}
/**
* Uses EntityAnimation packets to check for the excepted Animation (SwingArm) from the client.
* <br>
* <a href="https://wiki.vg/Protocol#Entity_Animation">Wiki.vg - EntityAnimation</a>
* <a href="https://wiki.vg/Protocol#Swing_Arm">Wiki.vg - SwingArm</a>
*/
private void sendArmAnimation() {
user.write(new EntityAnimationPacket(PLAYER_ENTITY_ID, EntityAnimationPacket.Type.SWING_MAIN_ARM));
waitingSwingArm = true;
}
private void markSuccess() {
// Pass the player to the next best verification handler
if (!user.isGeyser() && Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackVehicleHandler(user));
} else if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackCaptchaHandler(user));
} else {
// The player has passed all checks
finishVerification();
}
}
}
@@ -17,14 +17,15 @@
package xyz.jonesdev.sonar.common.fallback.verification;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_9;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
public final class FallbackVehicleHandler extends FallbackVerificationHandler {
@@ -32,191 +33,164 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
public FallbackVehicleHandler(final @NotNull FallbackUser user) {
super(user);
// Send the necessary packets to mount the player on the boat vehicle
user.delayedWrite(spawnBoatEntity);
user.delayedWrite(setBoatPassengers);
user.getChannel().flush();
spawnVehicle(State.IN_BOAT);
}
private int expectedTransactionId;
private int paddlePackets, inputPackets, positionPackets, rotationPackets, vehicleMovePackets;
private boolean expectMovement, inVoid, inMinecart, waitingSpawnMinecart, waitingRemoveMinecart;
private double minecartMotion, minecartY = IN_AIR_Y_POSITION;
private boolean waitingForStateChange;
private State state = State.WAITING;
private State nextState;
private int expectedKeepAliveId;
private int rotations, inputs, paddles, vehicleMoves;
private double boatMotion, boatY = IN_AIR_Y_POSITION;
private void markSuccess() {
// Pass the player to the next best verification handler
if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
// Either send the player to the CAPTCHA or finish the verification.
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
// Send the player to the CAPTCHA handler
decoder.setListener(new FallbackCaptchaHandler(user));
} else {
// The player has passed all checks
finishVerification();
}
}
@RequiredArgsConstructor
private enum State {
WAITING(false),
IN_BOAT(true),
IN_AIR_AFTER_BOAT(false),
IN_MINECART(true),
IN_AIR_AFTER_MINECART(false);
// We can abuse the entity remove mechanic and check for position packets when the entity dies
private void handleMovement(final double y, final boolean onGround) {
// The player cannot be on the ground if the Y position is less than 0
checkState(!onGround || !inVoid || waitingRemoveMinecart, "invalid ground state: " + y);
// Check the Y position of the player
final double minimumY = inVoid ? IN_VOID_Y_POSITION : IN_AIR_Y_POSITION;
checkState(y <= minimumY, "illegal y position: " + y + "/" + minimumY);
if (!waitingRemoveMinecart && !waitingSpawnMinecart
&& positionPackets++ > Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets()) {
if (user.isGeyser() || inVoid) {
// Mark this check as successful if the player sent a few position packets
markSuccess();
} else if (!inMinecart) {
// Continue checking the player by using Minecarts
spawnMinecart();
}
}
}
private void handleRotation() {
// Once the player sent enough packets, go to the next stage
final int minimumPackets = Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets();
if (paddlePackets > minimumPackets
&& inputPackets > minimumPackets
&& rotationPackets > minimumPackets
&& vehicleMovePackets > minimumPackets) {
// If the player is riding a Minecart, teleport the entity to < -100
// to see how the player reacts to the invalid position of the entity.
if (inMinecart) {
user.delayedWrite(teleportMinecart);
// We're using transactions to avoid false positives with lag
waitingRemoveMinecart = true;
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
user.delayedWrite(new TransactionPacket(0, expectedTransactionId, false));
user.getChannel().flush();
} else {
// If the player is riding a boat, remove the entity
// The next Y coordinate the player will send is going
// to be the vehicle spawn position (64 in this case).
user.write(removeBoat);
}
// Listen for next movement packet(s)
expectMovement = true;
}
rotationPackets++;
}
private void spawnMinecart() {
expectMovement = false;
paddlePackets = inputPackets = positionPackets = rotationPackets = 0;
// Use transaction packets to confirm that the entity has spawned
waitingSpawnMinecart = true;
minecartMotion = 0.03999999910593033D * 2 * -1;
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
user.delayedWrite(new TransactionPacket(0, expectedTransactionId, false));
user.delayedWrite(spawnMinecartEntity);
user.delayedWrite(setMinecartPassengers);
user.getChannel().flush();
private final boolean inVehicle;
}
@Override
public void handle(final @NotNull FallbackPacket packet) {
if (packet instanceof TransactionPacket) {
final TransactionPacket transaction = (TransactionPacket) packet;
// Make sure random transactions aren't counted
checkState(expectedTransactionId <= 0, "unexpected transaction");
// Make sure the window ID is valid
checkState(transaction.getWindowId() == 0, "wrong window ID " + transaction.getWindowId());
// Make sure the transaction was accepted
// This must - by vanilla protocol - always be accepted
checkState(transaction.isAccepted(), "didn't accept transaction");
// Also check if the transaction ID matches the expected ID
checkState(expectedTransactionId == transaction.getTransactionId(),
"expected T ID " + expectedTransactionId + ", but got " + transaction.getTransactionId());
if (packet instanceof KeepAlivePacket) {
final KeepAlivePacket keepAlivePacket = (KeepAlivePacket) packet;
// We're "lag compensating" the Minecart to avoid false positives
if (waitingSpawnMinecart) {
waitingSpawnMinecart = false;
inMinecart = true;
} else if (waitingRemoveMinecart) {
waitingRemoveMinecart = inMinecart = false;
inVoid = true;
positionPackets = 0;
}
// Check if we are expecting a KeepAlive packet
checkState(nextState != null, "invalid packet timing");
// Also check if the KeepAlive ID matches the expected ID
checkState(keepAlivePacket.getId() == expectedKeepAliveId,
"expected K ID " + expectedKeepAliveId + ", but got " + keepAlivePacket.getId());
expectedTransactionId = 0;
} else if (packet instanceof SetPlayerPositionRotationPacket) {
if (expectMovement) {
state = nextState;
nextState = null;
waitingForStateChange = false;
} else if (!waitingForStateChange) {
if (packet instanceof PaddleBoatPacket) {
if (state == State.IN_BOAT) {
paddles++;
}
} else if (packet instanceof VehicleMovePacket) {
if (state == State.IN_BOAT) {
final VehicleMovePacket vehicleMove = (VehicleMovePacket) packet;
// Check the Y position of the vehicle
checkState(vehicleMove.getY() <= IN_AIR_Y_POSITION, "bad vehicle y: " + vehicleMove.getY());
// Check the gravity of the vehicle
final double lastBoatMotion = boatMotion;
final double lastBoatY = boatY;
boatY = vehicleMove.getY();
boatMotion = boatY - lastBoatY;
final double predicted = lastBoatMotion - 0.03999999910593033D;
final double difference = Math.abs(boatMotion - predicted);
// Check if the difference between the predicted and actual motion is too large
checkState(difference < 1e-7, "bad vehicle gravity: " + predicted + "/" + boatMotion);
vehicleMoves++;
}
} else if (packet instanceof SetPlayerRotationPacket) {
if (state.inVehicle) {
rotations++;
// 1.21.2+ do not send PlayerInput packets when inside a vehicle.
// Handle it after SetPlayerRotationPacket to simulate vanilla behavior.
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
handlePlayerInput();
}
}
} else if (packet instanceof PlayerInputPacket) {
// 1.21.2+ send PlayerInput packets when the player starts sprinting, sneaking, etc.
if (state.inVehicle && user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
final PlayerInputPacket playerInput = (PlayerInputPacket) packet;
// Check if the player is sending invalid vehicle speed values
final float forward = Math.abs(playerInput.getForward());
final float sideways = Math.abs(playerInput.getSideways());
final float maxVehicleSpeed = /*user.isGeyser() ? 1 :*/ 0.98f;
checkState(forward <= maxVehicleSpeed, "illegal speed (f): " + forward);
checkState(sideways <= maxVehicleSpeed, "illegal speed (s): " + sideways);
handlePlayerInput();
}
} else if (packet instanceof SetPlayerPositionRotationPacket) {
final SetPlayerPositionRotationPacket posRot = (SetPlayerPositionRotationPacket) packet;
handleMovement(posRot.getY(), posRot.isOnGround());
}
} else if (packet instanceof SetPlayerPositionPacket) {
if (expectMovement) {
} else if (packet instanceof SetPlayerPositionPacket) {
final SetPlayerPositionPacket position = (SetPlayerPositionPacket) packet;
handleMovement(position.getY(), position.isOnGround());
}
} else if (packet instanceof SetPlayerRotationPacket) {
if (!expectMovement && !user.isGeyser()) {
handleRotation();
}
} else if (packet instanceof PaddleBoatPacket) {
checkState(!inMinecart, "invalid packet order (unexpected PaddleBoatPacket)");
paddlePackets++;
} else if (packet instanceof VehicleMovePacket) {
checkState(!inMinecart, "invalid packet order (unexpected VehicleMovePacket)");
final VehicleMovePacket vehicleMove = (VehicleMovePacket) packet;
if (!user.isGeyser()) {
// Check the Y position of the vehicle
checkState(vehicleMove.getY() <= IN_AIR_Y_POSITION,
"invalid vehicle y position: " + vehicleMove.getY());
// Predict the Y motion of the minecart
final double lastMinecartMotion = minecartMotion;
final double lastMinecartY = minecartY;
minecartY = vehicleMove.getY();
minecartMotion = minecartY - lastMinecartY;
final double predicted = lastMinecartMotion - 0.03999999910593033D;
final double difference = Math.abs(minecartMotion - predicted);
// Check if the difference between the predicted and actual motion is too large
checkState(difference < 1e-7,
"invalid vehicle gravity: " + predicted + "/" + minecartMotion + "/" + difference);
}
vehicleMovePackets++;
} else if (packet instanceof PlayerInputPacket) {
if (!expectMovement) {
final PlayerInputPacket playerInput = (PlayerInputPacket) packet;
}
}
// Check if the player is sending invalid vehicle speed values
final float forward = Math.abs(playerInput.getForward());
final float sideways = Math.abs(playerInput.getSideways());
final float maxVehicleSpeed = user.isGeyser() ? 1 : 0.98f;
checkState(forward <= maxVehicleSpeed, "illegal speed (f): " + forward);
checkState(sideways <= maxVehicleSpeed, "illegal speed (s): " + sideways);
private void spawnVehicle(final @NotNull State nextState) {
user.delayedWrite(nextState == State.IN_BOAT ? SPAWN_BOAT_ENTITY : SPAWN_MINECART_ENTITY);
user.delayedWrite(SET_VEHICLE_PASSENGERS);
prepareForNextState(nextState);
}
// Only mark this packet as correct if the player is not moving the vehicle
if (playerInput.isJump() || playerInput.isUnmount()) {
return;
}
private void prepareForNextState(final @NotNull State nextState) {
this.nextState = nextState;
waitingForStateChange = true;
expectedKeepAliveId = RANDOM.nextInt();
rotations = inputs = paddles = vehicleMoves = 0;
user.delayedWrite(new KeepAlivePacket(expectedKeepAliveId));
user.channel().flush();
}
// Bedrock users do not send SetPlayerPositionRotation and SetPlayerRotation packets
// Don't ask me why; Microsoft is doing some *fascinating* things with Bedrock...
if (user.isGeyser()) {
handleRotation();
} else {
checkState(rotationPackets >= inputPackets,
"illegal packet order; r/i " + rotationPackets + "/" + inputPackets);
}
private void markSuccess() {
// Pass the player to the next best verification handler
if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackCaptchaHandler(user));
} else {
finishVerification();
}
}
// 1.8 and below do not have PaddleBoat packets, so we simply exempt them from the PaddleBoat check.
// Clients don't send PaddleBoat & VehicleMovePacket packets while riding minecarts.
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0 || inMinecart) {
paddlePackets++;
vehicleMovePackets++;
} else if (!user.isGeyser()) {
checkState(paddlePackets >= inputPackets,
"illegal packet order; i/p " + inputPackets + "/" + paddlePackets);
checkState(vehicleMovePackets >= inputPackets,
"illegal packet order; i/v " + inputPackets + "/" + vehicleMovePackets);
}
inputPackets++;
}
private void handleMovement(final double y, final boolean isOnGround) {
// Make sure we're currently expecting movement
if (state.inVehicle || state == State.WAITING) {
return;
}
// Make sure the ground state and y position are correct
checkState(y <= boatY, "invalid y: " + y);
checkState(!isOnGround, "invalid ground state: " + y);
if (state == State.IN_AIR_AFTER_BOAT) {
spawnVehicle(State.IN_MINECART);
} else {
markSuccess();
}
}
private void handlePlayerInput() {
// 1.8 and below do not have PaddleBoat packets, so we simply exempt them from the PaddleBoat check.
// Clients also don't send PaddleBoat & VehicleMovePacket packets while riding minecarts.
if (user.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_9) || state == State.IN_MINECART) {
paddles++;
vehicleMoves++;
}
// Check for packet order
checkState(rotations >= inputs,
"illegal packet order; i/r " + inputs + "/" + rotations);
checkState(paddles >= inputs,
"illegal packet order; i/p " + inputs + "/" + paddles);
checkState(vehicleMoves >= inputs,
"illegal packet order; i/v " + inputs + "/" + vehicleMoves);
inputs++;
// Check if we've received more than the minimum number of packets
final int minimumPackets = Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets();
if (inputs > minimumPackets && rotations > minimumPackets
&& paddles > minimumPackets && vehicleMoves > minimumPackets) {
// Move on to the next stage
user.delayedWrite(REMOVE_VEHICLE);
prepareForNextState(state == State.IN_BOAT ? State.IN_AIR_AFTER_BOAT : State.IN_AIR_AFTER_MINECART);
}
}
}
@@ -25,19 +25,19 @@ import xyz.jonesdev.sonar.api.event.impl.UserBlacklistedEvent;
import xyz.jonesdev.sonar.api.event.impl.UserVerifyFailedEvent;
import xyz.jonesdev.sonar.api.event.impl.UserVerifySuccessEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarIntLengthEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketListener;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.util.Random;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_5;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.transferToOrigin;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.closeWith;
@RequiredArgsConstructor
public abstract class FallbackVerificationHandler implements FallbackPacketListener {
@@ -53,22 +53,21 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
user.getFingerprint(), user.getLoginTimer().getStart()));
// Call the VerifySuccessEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(user, user.getLoginTimer()));
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(user));
// If enabled, transfer the player back to the origin server.
// This feature was introduced by Mojang in Minecraft version 1.20.5.
if (transferToOrigin != null
&& user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
if (transferToOrigin != null && user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
// Send the transfer packet to the player (and close the channel if on Java Edition)
if (user.isGeyser()) {
user.write(transferToOrigin);
// Make sure we cannot receive any more packets from the player
user.getPipeline().remove(FallbackPacketDecoder.class);
user.getPipeline().remove(FallbackPacketEncoder.class);
user.getPipeline().remove(FallbackVarInt21FrameDecoder.class);
user.getPipeline().remove(FallbackVarIntLengthEncoder.class);
user.channel().pipeline().remove(FallbackPacketDecoder.class);
user.channel().pipeline().remove(FallbackPacketEncoder.class);
user.channel().pipeline().remove(FallbackVarInt21FrameDecoder.class);
user.channel().pipeline().remove(FallbackVarIntLengthEncoder.class);
} else {
closeWith(user.getChannel(), user.getProtocolVersion(), transferToOrigin);
ProtocolUtil.closeWith(user.channel(), user.getProtocolVersion(), transferToOrigin);
}
} else {
// Disconnect player with the verification success message
@@ -94,7 +93,7 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.failed")
.replace("<username>", user.getUsername())
.replace("<ip>", Sonar.get().getConfig().formatAddress(user.getInetAddress()))
.replace("<protocol>", String.valueOf(user.getProtocolVersion().getProtocol()))
.replace("<protocol>", user.getProtocolVersion().getName())
.replace("<reason>", reason));
}
@@ -124,7 +123,7 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.blacklisted")
.replace("<username>", user.getUsername())
.replace("<ip>", Sonar.get().getConfig().formatAddress(user.getInetAddress()))
.replace("<protocol>", String.valueOf(user.getProtocolVersion().getProtocol())));
.replace("<protocol>", user.getProtocolVersion().getName()));
}
}
@@ -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()))));
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more